+2011-06-10 Sampo Niskanen
+
+ * Released version 1.1.5
+
+2011-05-16 Sampo Niskanen
+
+ * Updated about dialog
+
+2011-04-15 Sampo Niskanen
+
+ * Removed native printing support
+
+2011-03-05 Sampo Niskanen
+
+ * Released version 1.1.4
+
2011-01-20 Sampo Niskanen
* Initial i18n support
OpenRocket - A model rocket simulator
-Copyright (C) 2007-2010 Sampo Niskanen
+Copyright (C) 2007-2011 Sampo Niskanen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+OpenRocket 1.1.5 (2011-06-10):
+-------------------------------
+
+Removed native printing support. Printing is now handled via PDF
+viewer, which should make printing much more reliable and less
+bug-prone.
+
+
+OpenRocket 1.1.4 (2011-03-05):
+-------------------------------
+
+Initial printing support by Doug Pedrick, and various bug fixes.
+
+
OpenRocket 1.1.3 (2010-10-06):
-------------------------------
# The OpenRocket build version
-build.version=1.1.4pre
+build.version=1.1.6pre
# The source of the package. When building a package for a specific
<target name="todo" depends="checktodo"/>
<target name="checktodo">
<tempfile property="todo.file" prefix="checktodo-"/>
- <echo>Checking project for critical TODOs.</echo>
+ <echo>Checking project for FIXMEs.</echo>
<concat destfile="${todo.file}">
<fileset dir="${src.dir}">
<include name="**/*.java"/>
</fileset>
<filterchain>
<linecontainsregexp>
- <regexp pattern="TODO:.*CRITICAL"/>
+ <regexp pattern="(FIXME|TODO:.*CRITICAL)"/>
</linecontainsregexp>
</filterchain>
</concat>
dlg.but.cancel = Cancel
dlg.but.close = Close
+
+! General file type names
+filetypes.pdf = PDF files
+
+
! About Dialog
-about.dlg.but.close = Close
-about.dlg.lbl.translation =
-about.dlg.lbl.transwebsite =
+AboutDialog.lbl.version = Version
+! The texts below provide additional credits for the translation maintainer
+! - In AboutDialog.lbl.translation replace "English" with the current language.
+! - AboutDialog.lbl.translator is the translator / group name (may be empty)
+! - AboutDialog.lbl.translatorWebsite is a URL to the translator / group (may be empty)
+! - AboutDialog.lbl.translatorIcon is the file name of an icon under pix/translators/ (may be empty)
+AboutDialog.lbl.translation = English translation by:
+AboutDialog.lbl.translator =
+AboutDialog.lbl.translatorWebsite =
+AboutDialog.lbl.translatorIcon =
+
+
+! Print dialog
+PrintDialog.title = Print or export
+PrintDialog.but.previewAndPrint = Preview & Print
+PrintDialog.checkbox.showByStage = Show by stage
+PrintDialog.lbl.selectElements = Select elements to include:
+printdlg.but.saveaspdf = Save as PDF
+printdlg.but.preview = Preview
+printdlg.but.settings = Settings
+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.
+
! Bug Report dialog
bugreport.dlg.title = Bug report
exdesigndlg.lbl.Exampledesignsnotfound = Example designs could not be found.
exdesigndlg.lbl.Examplesnotfound = Examples not found
-! Print panel dialog
-printdlg.but.saveaspdf = Save as PDF
-printdlg.but.preview = Preview
-printdlg.but.settings = Settings
! Material edit panel
matedtpan.but.new = New
main.menu.file.openExample = Open example...
main.menu.file.save = Save
main.menu.file.saveAs = Save as...
-main.menu.file.print = Print...
+main.menu.file.print = Print / Export PDF...
main.menu.file.close = Close
main.menu.file.quit = Quit
Databases.materials.Silk = Silk
Databases.materials.Paperoffice = Paper (office)
Databases.materials.Cellophane = Cellophane
-Databases.materials.Creape paper = Cr\u00eape paper
+Databases.materials.Crepepaper = Cr\u00eape paper
! LINE_MATERIAL
Databases.materials.Threadheavy-duty = Thread (heavy-duty)
Databases.materials.Elasticcordround2mm = Elastic cord (round 2mm, 1/16 in)
Shape.Ellipsoid.desc1 = An ellipsoidal nose cone has a profile of a half-ellipse with major axes of lengths 2×<i>Length</i> and <i>Diameter</i>.
Shape.Ellipsoid.desc2 = An ellipsoidal transition has a profile of a half-ellipse with major axes of lengths 2×<i>Length</i> and <i>Diameter</i>. If the transition is not clipped, then the profile is extended at the center by the corresponding radius.
Shape.Powerseries = Power series
-Shape.Powerseries.descA1 = A power series nose cone has a profile of <i>Radius</i> × (<i>x</i> / <i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter. For <i>k</i>=0.5 this is a <b>
-Shape.Powerseries.descA2 = -power</b> or <b>parabolic</b> nose cone, for <i>k</i>=0.75 a <b>
-Shape.Powerseries.descA3 = -power</b>, and for <i>k</i>=1 a <b>conical</b> nose cone.
-Shape.Powerseries.descB1 = A power series transition has a profile of <i>Radius</i> × (<i>x</i> / <i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter. For <i>k</i>=0.5 the transition is <b>
-Shape.Powerseries.descB2 = -power</b> or <b>parabolic</b>, for <i>k</i>=0.75 a <b>
-Shape.Powerseries.descB3 = -power</b>, and for <i>k</i>=1 <b>conical</b>.
+Shape.Powerseries.desc1 = A power series nose cone has a profile of <i>Radius</i> × (<i>x</i> / <i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter. For <i>k</i>=0.5 this is a <b>\u00BD-power</b> or <b>parabolic</b> nose cone, for <i>k</i>=0.75 a <b>\u00BE-power</b>, and for <i>k</i>=1 a <b>conical</b> nose cone.
+Shape.Powerseries.desc2 = A power series transition has a profile of <i>Radius</i> × (<i>x</i> / <i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter. For <i>k</i>=0.5 the transition is <b>\u00BD-power</b> or <b>parabolic</b>, for <i>k</i>=0.75 a <b>\u00BE-power</b>, and for <i>k</i>=1 <b>conical</b>.
Shape.Parabolicseries = Parabolic series
Shape.Parabolicseries.desc1 = A parabolic series nose cone has a profile of a parabola. The shape parameter defines the segment of the parabola to utilize. The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> nose cone.
Shape.Parabolicseries.desc2 = A parabolic series transition has a profile of a parabola. The shape parameter defines the segment of the parabola to utilize. The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube at the aft end, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> transition.
Databases.materials.Silk = Seide\r
Databases.materials.Paperoffice = Papier (Büro)\r
Databases.materials.Cellophane = Zellophan\r
-Databases.materials.Creape paper = Krepppapier\r
+Databases.materials.Crepepaper = Krepppapier\r
! LINE_MATERIAL\r
Databases.materials.Threadheavy-duty = Faden (stark)\r
Databases.materials.Elasticcordround2mm = Elastikband (rund, 2mm, 1/16 in)\r
! Component add buttons
compaddbuttons.Bodycompandfinsets = Componentes del fuselaje y aletas
compaddbuttons.Nosecone = Ojiva
-compaddbuttons.Bodytube = Tubo del fuselaje
+compaddbuttons.Bodytube = Tubo del\nfuselaje
compaddbuttons.Transition = Transición
compaddbuttons.Trapezoidal = Trapezoidal
compaddbuttons.Elliptical = ElÃptico
compaddbuttons.Freeform = Forma libre
-compaddbuttons.Launchlug = GuÃa en el fuselaje
+compaddbuttons.Launchlug = GuÃa en el\nfuselaje
compaddbuttons.Innercomponent = Componente interior
-compaddbuttons.Innertube = Tubo interior
+compaddbuttons.Innertube = Tubo\ninterior
compaddbuttons.Coupler = Acoplador
compaddbuttons.Centeringring = Arandela\nde centraje
compaddbuttons.Bulkhead = Cámara
compaddbuttons.Massobjects = Objeto masa
compaddbuttons.Parachute = ParacaÃdas
compaddbuttons.Streamer = Banderola
-compaddbuttons.Shockcord = Tirante de suspensión
+compaddbuttons.Shockcord = Tirante de\nsuspensión
compaddbuttons.Masscomponent = Componente\nmasa
compaddbuttons.Donotaskmeagain = No me pregunte de nuevo
compaddbuttons.Selectcomppos = Seleccionar la posición del componente
Databases.materials.Silk = Seda
Databases.materials.Paperoffice = Papel (oficina)
Databases.materials.Cellophane = Celofán
-Databases.materials.Creape paper = Cr\u00eape Papel
+Databases.materials.Crepepaper = Cr\u00eape Papel
! LINE_MATERIAL
Databases.materials.Threadheavy-duty = Trenzado (Alta resistencia)
Databases.materials.Elasticcordround2mm = Cordón elástico (aprox. 2mm, 1/16 in)
dlg.but.close = Fermer\r
\r
! About Dialog\r
-about.dlg.but.close = Fermer\r
-about.dlg.lbl.translation = Traduit en français par Tripoli France\r
-about.dlg.lbl.transwebsite = http://tripoli.france.free.fr/\r
+AboutDialog.lbl.translation = Traduit en français par\r
+AboutDialog.lbl.translator = Tripoli France\r
+AboutDialog.lbl.translatorWebsite = http://tripoli.france.free.fr/\r
\r
! Bug Report dialog\r
bugreport.dlg.title = Rapport d'erreurs\r
! Component add buttons\r
compaddbuttons.Bodycompandfinsets = Pièces du corps et ailerons\r
compaddbuttons.Nosecone = Cône\r
-compaddbuttons.Bodytube = Tube du corps\r
+compaddbuttons.Bodytube = Tube du\ncorps\r
compaddbuttons.Transition = Transition\r
compaddbuttons.Trapezoidal = Trapézoïdale\r
compaddbuttons.Elliptical = Elliptique\r
compaddbuttons.Freeform = Forme libre\r
-compaddbuttons.Launchlug = Tube de guidage\r
+compaddbuttons.Launchlug = Tube de\nguidage\r
compaddbuttons.Innercomponent = Pièces internes\r
compaddbuttons.Innertube = Tube interne\r
compaddbuttons.Coupler = Coupleur\r
-compaddbuttons.Centeringring = Anneau\nde centrage\r
+compaddbuttons.Centeringring = Anneau de\ncentrage\r
compaddbuttons.Bulkhead = Cloison\r
-compaddbuttons.Engineblock = Bague \nde rétention\nmoteur\r
+compaddbuttons.Engineblock = Bague de\nrétention\nmoteur\r
compaddbuttons.Massobjects = Pièces pleines\r
compaddbuttons.Parachute = Parachute\r
compaddbuttons.Streamer = Banderole\r
-compaddbuttons.Shockcord = Cordon amortisseur\r
+compaddbuttons.Shockcord = Cordon\namortisseur\r
compaddbuttons.Masscomponent = Poids\r
compaddbuttons.Donotaskmeagain = Ne plus me poser la question\r
compaddbuttons.Selectcomppos = Position de la pièce\r
Databases.materials.Silk = Soie\r
Databases.materials.Paperoffice1 = Papier (bureau)\r
Databases.materials.Cellophane = Cellophane\r
-Databases.materials.Creapepaper = Cr\u00eape paper\r
+Databases.materials.Crepepaper = Cr\u00eape paper\r
! LINE_MATERIAL\r
Databases.materials.Threadheavy-duty = Fil(résistant)\r
Databases.materials.Elasticcordround2mm = Corde Elastique (ronde 2mm, 1/16 in)\r
11. Tag the version in SVN, URL:
https://openrocket.svn.sourceforge.net/svnroot/openrocket/tags/Release_1.1.x
12. Upload JAR and source distribution and ReleaseNotes to Sourceforge
- - Project Admin -> File Manager
- - create new version directory under /openrocket
- - upload JAR, ZIP and ReleaseNotes
- - select ReleaseNotes properties, set as release notes
- - select JAR properties, set release note file and default downloads
- - select ZIP properties, set release note file
+ - Files -> Add Folder -> Add File
13. Update HTML to web server:
scp * plaa,openrocket@web.sourceforge.net:htdocs/
14. Test downloading from Sourceforge and web site
public class Databases {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
/* Static implementations of specific databases: */
/**
SURFACE_MATERIAL.add(new Material.Surface(trans.get("Databases.materials.Silk"), 0.060, false));
SURFACE_MATERIAL.add(new Material.Surface(trans.get("Databases.materials.Paperoffice"), 0.080, false));
SURFACE_MATERIAL.add(new Material.Surface(trans.get("Databases.materials.Cellophane"), 0.018, false));
- SURFACE_MATERIAL.add(new Material.Surface(trans.get("Databases.materials.Creapepaper"), 0.025, false));
+ SURFACE_MATERIAL.add(new Material.Surface(trans.get("Databases.materials.Crepepaper"), 0.025, false));
//// Thread (heavy-duty)
LINE_MATERIAL.add(new Material.Line(trans.get("Databases.materials.Threadheavy-duty"), 0.0003, false));
--- /dev/null
+package net.sf.openrocket.gui.components;
+
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JButton;
+import javax.swing.JColorChooser;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * A color chooser button. The currently selected color can be queried or set using the
+ * {@link #getSelectedColor()} and {@link #setSelectedColor(Color)}, and changes listened
+ * to by listening to property events with property name {@link #COLOR_KEY}.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ColorChooserButton extends JButton {
+ private static final LogHelper log = Application.getLogger();
+
+ public static final String COLOR_KEY = "selectedColor";
+
+
+ public ColorChooserButton(Color initial) {
+
+ setSelectedColor(initial);
+
+ // Add action listener that opens color chooser dialog
+ this.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Activating color chooser");
+ final JColorChooser chooser = new JColorChooser(getSelectedColor());
+ chooser.setPreviewPanel(new JPanel());
+ final JDialog dialog = JColorChooser.createDialog(ColorChooserButton.this, "Select color", true,
+ chooser, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e2) {
+ Color c = chooser.getColor();
+ log.user("User selected color " + c);
+ setSelectedColor(chooser.getColor());
+ }
+ }, null);
+ log.info("Closing color chooser");
+ dialog.setVisible(true);
+ }
+ });
+
+ }
+
+
+ public void addColorPropertyChangeListener(PropertyChangeListener listener) {
+ this.addPropertyChangeListener(COLOR_KEY, listener);
+ }
+
+ public void setSelectedColor(Color c) {
+ log.debug("Selecting color " + c);
+ this.setIcon(new ColorIcon(c));
+ this.putClientProperty(COLOR_KEY, c);
+ }
+
+ public Color getSelectedColor() {
+ return (Color) this.getClientProperty(COLOR_KEY);
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.gui.components;
+
+import java.awt.Color;
+
+import javax.swing.Icon;
+
+/**
+ * An Icon that displays a specific color, suitable for drawing into a button.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ColorIcon implements Icon {
+ private final Color color;
+
+ public ColorIcon(Color c) {
+ this.color = c;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return 15;
+ }
+
+ @Override
+ public int getIconWidth() {
+ return 25;
+ }
+
+ @Override
+ public void paintIcon(java.awt.Component c, java.awt.Graphics g, int x, int y) {
+ g.setColor(color);
+ g.fill3DRect(x, y, getIconWidth(), getIconHeight(), false);
+ }
+
+}
import java.awt.Color;
-import java.awt.Component;
-import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.util.List;
import javax.swing.BorderFactory;
-import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import net.sf.openrocket.gui.adaptors.EnumModel;
import net.sf.openrocket.gui.adaptors.MaterialModel;
import net.sf.openrocket.gui.components.BasicSlider;
+import net.sf.openrocket.gui.components.ColorIcon;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
import net.sf.openrocket.gui.components.UnitSelector;
// Component color and "Use default color" checkbox
if (colorButton != null && colorDefault != null) {
- colorButton.setIcon(new ColorIcon(component.getColor()));
+ colorButton.setIcon(new ColorIcon(getColor()));
if ((component.getColor() == null) != colorDefault.isSelected())
colorDefault.setSelected(component.getColor() == null);
//// Component color:
panel.add(new JLabel(trans.get("RocketCompCfg.lbl.Componentcolor")), "gapleft para, gapright 10lp");
- colorButton = new JButton(new ColorIcon(component.getColor()));
+ colorButton = new JButton(new ColorIcon(getColor()));
colorButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
}
-
+ private Color getColor() {
+ Color c = component.getColor();
+ if (c == null) {
+ c = Prefs.getDefaultColor(component.getClass());
+ }
+ return c;
+ }
+
+
protected JPanel shoulderTab() {
JPanel panel = new JPanel(new MigLayout("fill"));
}
- private class ColorIcon implements Icon {
- private final Color color;
-
- public ColorIcon(Color c) {
- this.color = c;
- }
-
- @Override
- public int getIconHeight() {
- return 15;
- }
-
- @Override
- public int getIconWidth() {
- return 25;
- }
-
- @Override
- public void paintIcon(Component c, Graphics g, int x, int y) {
- if (color == null) {
- g.setColor(Prefs.getDefaultColor(component.getClass()));
- } else {
- g.setColor(color);
- }
- g.fill3DRect(x, y, getIconWidth(), getIconHeight(), false);
- }
-
- }
-
protected void register(Invalidatable model) {
this.invalidatables.add(model);
}
}
}
-}
+}
\ No newline at end of file
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
+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.components.URLLabel;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
public static final String OPENROCKET_URL = "http://openrocket.sourceforge.net/";
private static final Translator trans = Application.getTranslator();
+ private static final String CREDITS = "<html><center>" +
+ "<font size=\"+1\"><b>OpenRocket has been developed by:</b></font><br><br>" +
+ "Sampo Niskanen (main developer)<br>" +
+ "Doug Pedrick (RockSim file format, printing)<br>" +
+ "Boris du Reau (internationalization, translation lead)<br><br>" +
+ "<b>OpenRocket utilizes the following libraries:</b><br><br>" +
+ "MiG Layout (http://www.miglayout.com/)<br>" +
+ "JFreeChart (http://www.jfree.org/jfreechart/)<br>" +
+ "iText (http://www.itextpdf.com/)";
+
public AboutDialog(JFrame parent) {
super(parent, true);
final String version = Prefs.getVersion();
JPanel panel = new JPanel(new MigLayout("fill"));
+ JPanel sub;
- panel.add(new JLabel(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket")),
- "spany, top");
+
+ // OpenRocket logo
+ panel.add(new JLabel(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket")), "top");
- panel.add(new StyledLabel("OpenRocket", 20), "ax 50%, growy, wrap para");
- panel.add(new StyledLabel("Version " + version, 3), "ax 50%, growy, wrap rel");
+
+ // OpenRocket version info + copyright
+ sub = new JPanel(new MigLayout("fill"));
- // String source = Prefs.getBuildSource();
- // if (!Prefs.DEFAULT_BUILD_SOURCE.equalsIgnoreCase(source)) {
- // panel.add(new StyledLabel("Distributed by " + source, -1),
- // "ax 50%, growy, wrap para");
- // } else {
- // panel.add(new StyledLabel(" ", -1), "ax 50%, growy, wrap para");
- // }
+ sub.add(new StyledLabel("OpenRocket", 20), "ax 50%, growy, wrap para");
+ sub.add(new StyledLabel(trans.get("lbl.version").trim() + " " + version, 3), "ax 50%, growy, wrap rel");
+ sub.add(new StyledLabel("Copyright " + Chars.COPY + " 2007-2011 Sampo Niskanen"), "ax 50%, growy, wrap para");
- panel.add(new StyledLabel("Copyright " + Chars.COPY + " 2007-2010 Sampo Niskanen"),
- "ax 50%, growy, wrap para");
+ sub.add(new URLLabel(OPENROCKET_URL), "ax 50%, growy, wrap para");
+ panel.add(sub, "grow");
- panel.add(new URLLabel(OPENROCKET_URL), "ax 50%, growy, wrap para");
+
+ // Translation information (if present)
+ String translation = trans.get("lbl.translation").trim();
+ String translator = trans.get("lbl.translator").trim();
+ String translatorWebsite = trans.get("lbl.translatorWebsite").trim();
+ String translatorIcon = trans.get("lbl.translatorIcon").trim();
- //// translations author if language is not English
- String translator = trans.get("about.dlg.lbl.translation").trim();
- String translatorUrl = trans.get("about.dlg.lbl.transwebsite").trim();
- if (translator.length() > 0) {
- panel.add(new StyledLabel(translator), "ax 50%, growy, wrap para");
- if (translatorUrl.length() > 0) {
- panel.add(new URLLabel(translatorUrl), "ax 50%, growy, wrap para");
+ if (translator.length() > 0 || translatorWebsite.length() > 0 || translatorIcon.length() > 0) {
+ sub = new JPanel(new MigLayout("fill"));
+
+ sub.add(new StyledLabel(translation, Style.BOLD), "ax 50%, growy, wrap para");
+
+ if (translatorIcon.length() > 0) {
+ sub.add(new JLabel(Icons.loadImageIcon("pix/translators/" + translatorIcon, translator)),
+ "ax 50%, growy, wrap para");
+ }
+ if (translator.length() > 0) {
+ sub.add(new JLabel(translator), "ax 50%, growy, wrap rel");
+ }
+ if (translatorWebsite.length() > 0) {
+ sub.add(new URLLabel(translatorWebsite), "ax 50%, growy, wrap para");
}
+
+ panel.add(sub);
}
+
+ DescriptionArea info = new DescriptionArea(5);
+ info.setText(CREDITS);
+ panel.add(info, "newline, width 10px, height 100lp, grow, spanx, wrap para");
+
+ // JTextArea area = new JTextArea(CREATORS);
+ // area.setEditable(false);
+ // area.setLineWrap(true);
+ // area.setWrapStyleWord(true);
+ // panel.add(new JScrollPane(area), "width 10px, height 100lp, grow, spanx, wrap para");
+
+
//Close button
- JButton close = new JButton(trans.get("dlg.but.close"));
+ JButton close = new JButton(trans.get("button.close"));
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GUIUtil.setDisposableDialogOptions(this, close);
}
-
}
/*
- * PrintDialog.java
+ * PrintPanel.java
*/
package net.sf.openrocket.gui.dialogs;
+import java.awt.Desktop;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.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.print.PDFPrintStreamDoc;
-import net.sf.openrocket.gui.print.PrintUtilities;
+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;
+import net.sf.openrocket.gui.print.TemplateProperties;
+import net.sf.openrocket.gui.print.components.CheckTreeManager;
+import net.sf.openrocket.gui.print.components.RocketPrintTree;
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 sun.print.ServiceDialog;
-
-import javax.print.DocFlavor;
-import javax.print.DocPrintJob;
-import javax.print.PrintException;
-import javax.print.PrintService;
-import javax.print.PrintServiceLookup;
-import javax.print.attribute.Attribute;
-import javax.print.attribute.AttributeSet;
-import javax.print.attribute.HashPrintRequestAttributeSet;
-import javax.print.attribute.PrintRequestAttributeSet;
-import javax.print.attribute.standard.Destination;
-import javax.print.attribute.standard.Fidelity;
-import javax.swing.JDialog;
-import javax.swing.JMenu;
-import javax.swing.JTabbedPane;
-import java.awt.Component;
-import java.awt.Container;
-import java.awt.Dialog;
-import java.awt.GraphicsConfiguration;
-import java.awt.GraphicsDevice;
-import java.awt.GraphicsEnvironment;
-import java.awt.HeadlessException;
-import java.awt.Rectangle;
-import java.io.ByteArrayOutputStream;
+import net.sf.openrocket.util.GUIUtil;
+import net.sf.openrocket.util.Prefs;
/**
- * This class is not a dialog by inheritance, but is by delegation. It front-ends a java print dialog by
- * augmenting it with application specific (rocket) settings.
+ * This class isolates the Swing components used to create a panel that is added to a standard Java print dialog.
*/
-public class PrintDialog {
-
- /**
- * The service UI dialog.
- */
- private ServiceDialog dialog;
-
- /**
- * A javax doc flavor specific for printing PDF documents.
- */
- private static final DocFlavor.INPUT_STREAM PDF = DocFlavor.INPUT_STREAM.PDF;
-
- private static final Translator trans = Application.getTranslator();
-
- /**
- * Construct a print dialog using an Open Rocket document - which contains the rocket data to ultimately be
- * printed.
- *
- * @param orDocument the rocket container
- */
- public PrintDialog (OpenRocketDocument orDocument) {
- PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
- PrintService svc = PrintServiceLookup.lookupDefaultPrintService();
- PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
- attrs.add(PrintUtilities.getDefaultMedia().getMediaSizeName());
-
- final PrintPanel panel = new PrintPanel(orDocument, this);
- PrintService ps = printDialog(null, 100, 100, services, svc, PDF, attrs, panel);
- if (ps != null) {
- DocPrintJob dpj = ps.createPrintJob();
- try {
- System.err.println(attrs.size());
- ByteArrayOutputStream baos = panel.generateReport();
- dpj.print(new PDFPrintStreamDoc(baos, null), attrs);
- }
- catch (PrintException e) {
- e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
- }
- }
- }
-
- /**
- * Get the set of attributes from the service ui print dialog.
- *
- * @return a set of print attributes
- */
- PrintRequestAttributeSet getAttributes () {
- return dialog.getAttributes();
- }
-
- /**
- * Get the service ui dialog. This is the actual dialog that gets displayed - we co-opt it for adding a rocket
- * specific tab.
- *
- * @return the Java service ui print dialog
- */
- JDialog getDialog () {
- return dialog;
- }
-
- /**
- * Mimics the ServiceUI.printDialog method, but with enhancements for our own print settings tab.
- *
- * @param gc used to select screen. null means primary or default screen.
- * @param x location of dialog including border in screen coordinates
- * @param y location of dialog including border in screen coordinates
- * @param services to be browsable, must be non-null.
- * @param defaultService - initial PrintService to display.
- * @param flavor - the flavor to be printed, or null.
- * @param attributes on input is the initial application supplied preferences. This cannot be null but may be
- * empty. On output the attributes reflect changes made by the user.
- * @param addnl a panel to be added, as a tab, to the internal tabbed pane of the resulting print dialog
- *
- * @return print service selected by the user, or null if the user cancelled the dialog.
- *
- * @throws HeadlessException if GraphicsEnvironment.isHeadless() returns true.
- * @throws IllegalArgumentException if services is null or empty, or attributes is null, or the initial PrintService
- * is not in the list of browsable services.
- */
- private PrintService printDialog (GraphicsConfiguration gc,
- int x, int y,
- PrintService[] services,
- PrintService defaultService,
- DocFlavor flavor,
- PrintRequestAttributeSet attributes,
- PrintPanel addnl)
- throws HeadlessException {
- int defaultIndex = -1;
-
- if (GraphicsEnvironment.isHeadless()) {
- throw new HeadlessException();
- }
- else if ((services == null) || (services.length == 0)) {
- throw new IllegalArgumentException("services must be non-null " +
- "and non-empty");
- }
- else if (attributes == null) {
- throw new IllegalArgumentException("attributes must be non-null");
- }
-
- if (defaultService != null) {
- for (int i = 0; i < services.length; i++) {
- if (services[i].equals(defaultService)) {
- defaultIndex = i;
- break;
- }
- }
-
- if (defaultIndex < 0) {
- throw new IllegalArgumentException("services must contain " +
- "defaultService");
- }
- }
- else {
- defaultIndex = 0;
- }
-
- Rectangle gcBounds = (gc == null) ? GraphicsEnvironment.
- getLocalGraphicsEnvironment().getDefaultScreenDevice().
- getDefaultConfiguration().getBounds() : gc.getBounds();
-
- dialog = new ServiceDialog(gc,
- x + gcBounds.x,
- y + gcBounds.y,
- services, defaultIndex,
- flavor, attributes,
- (Dialog) null);
- Rectangle dlgBounds = dialog.getBounds();
-
- // get union of all GC bounds
- GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
- GraphicsDevice[] gs = ge.getScreenDevices();
- for (GraphicsDevice g : gs) {
- gcBounds = gcBounds.union(g.getDefaultConfiguration().getBounds());
- }
-
- // if portion of dialog is not within the gc boundary
- if (!gcBounds.contains(dlgBounds)) {
- // put in the center relative to parent frame/dialog
- dialog.setLocationRelativeTo(null);
- }
- if (addnl != null && addnl.getTitle() != null) {
- JTabbedPane tp = (JTabbedPane) getDescendantOfClass(JTabbedPane.class, dialog);
- tp.add(addnl, addnl.getTitle(), 0);
- tp.setSelectedIndex(0);
- }
+public class PrintDialog extends JDialog implements TreeSelectionListener {
+
+ private static final LogHelper log = Application.getLogger();
+ private static final Translator trans = Application.getTranslator();
+
+ private final Desktop desktop;
+
+ private final RocketPrintTree stagedTree;
+ private final RocketPrintTree noStagedTree;
+ private OpenRocketDocument document;
+ private RocketPrintTree currentTree;
+
+ private JButton previewButton;
+ private JButton saveAsPDF;
+ private JButton cancel;
+
+ /**
+ * Constructor.
+ *
+ * @param orDocument the OR rocket container
+ */
+ public PrintDialog(Window parent, OpenRocketDocument orDocument) {
+ super(parent, trans.get("title"), ModalityType.APPLICATION_MODAL);
+
- dialog.setVisible(true);
+ JPanel panel = new JPanel(new MigLayout("fill, gap rel unrel"));
+ this.add(panel);
+
- if (dialog.getStatus() == ServiceDialog.APPROVE) {
- PrintRequestAttributeSet newas = dialog.getAttributes();
- Class dstCategory = Destination.class;
- Class fdCategory = Fidelity.class;
+ // before any Desktop APIs are used, first check whether the API is
+ // supported by this particular VM on this particular host
+ if (Desktop.isDesktopSupported()) {
+ desktop = Desktop.getDesktop();
+ } else {
+ desktop = null;
+ }
+
+ document = orDocument;
+ Rocket rocket = orDocument.getRocket();
+
+ noStagedTree = RocketPrintTree.create(rocket.getName());
+ noStagedTree.setShowsRootHandles(false);
+ CheckTreeManager ctm = new net.sf.openrocket.gui.print.components.CheckTreeManager(noStagedTree);
+ ctm.addTreeSelectionListener(this);
+
+ final int stages = rocket.getStageCount();
+
- if (attributes.containsKey(dstCategory) &&
- !newas.containsKey(dstCategory)) {
- attributes.remove(dstCategory);
- }
+ JLabel label = new JLabel(trans.get("lbl.selectElements"));
+ panel.add(label, "wrap unrel");
+
+ // Create the tree
+ if (stages > 1) {
+ stagedTree = RocketPrintTree.create(rocket.getName(), rocket.getChildren());
+ ctm = new CheckTreeManager(stagedTree);
+ stagedTree.setShowsRootHandles(false);
+ ctm.addTreeSelectionListener(this);
+ } else {
+ stagedTree = noStagedTree;
+ }
+ currentTree = stagedTree;
+
+ // Add the tree to the UI
+ final JScrollPane scrollPane = new JScrollPane(stagedTree);
+ panel.add(scrollPane, "width 400lp, height 200lp, grow, wrap para");
+
- attributes.addAll(newas);
+ // Checkboxes and buttons
+ final JCheckBox sortByStage = new JCheckBox(trans.get("checkbox.showByStage"));
+ sortByStage.setEnabled(stages > 1);
+ sortByStage.setSelected(stages > 1);
+ sortByStage.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (sortByStage.isEnabled()) {
+ if (((JCheckBox) e.getSource()).isSelected()) {
+ scrollPane.setViewportView(stagedTree);
+ stagedTree.setExpandsSelectedPaths(true);
+ currentTree = stagedTree;
+ }
+ else {
+ scrollPane.setViewportView(noStagedTree);
+ noStagedTree.setExpandsSelectedPaths(true);
+ currentTree = noStagedTree;
+ }
+ }
+ }
+ });
+ panel.add(sortByStage, "aligny top, split");
+
- Fidelity fd = (Fidelity) attributes.get(fdCategory);
- if (fd != null) {
- if (fd == Fidelity.FIDELITY_TRUE) {
- removeUnsupportedAttributes(dialog.getPrintService(),
- flavor, attributes);
- }
- }
- return dialog.getPrintService();
- }
- else {
- return null;
- }
- }
+ panel.add(new JPanel(), "growx");
+
- private Component getDescendantOfClass (Class c, Container cont) {
- if (c == null || cont == null) {
- return null;
- }
- Component[] children = (cont instanceof JMenu)
- ? ((JMenu) cont).getMenuComponents()
- : cont.getComponents();
- for (int i = 0, n = children.length; i < n; i++) {
- Component comp = children[i];
- if (c.isInstance(comp)) {
- return comp;
- }
- comp = getDescendantOfClass(c, (Container) comp);
- if (comp != null) {
- return comp;
- }
- }
- return null;
- }
+ JButton settingsButton = new JButton(trans.get("printdlg.but.settings"));
+ settingsButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ PrintSettings settings = Prefs.getPrintSettings();
+ log.debug("settings=" + settings);
+ PrintSettingsDialog settingsDialog = new PrintSettingsDialog(PrintDialog.this, settings);
+ settingsDialog.setVisible(true);
+ Prefs.setPrintSettings(settings);
+ }
+ });
+ panel.add(settingsButton, "wrap para");
+
- /**
- * Removes any attributes from the given AttributeSet that are unsupported by the given PrintService/DocFlavor
- * combination.
- *
- * @param ps the print service for which unsupported attributes will be determined
- * @param flavor the document flavor; PDF in our case
- * @param aset the set of attributes requested
- */
- private static void removeUnsupportedAttributes (PrintService ps,
- DocFlavor flavor,
- AttributeSet aset) {
- AttributeSet asUnsupported = ps.getUnsupportedAttributes(flavor,
- aset);
+ previewButton = new JButton(trans.get("but.previewAndPrint"));
+ previewButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ onPreview();
+ PrintDialog.this.setVisible(false);
+ }
+ });
+ panel.add(previewButton, "split, right, gap para");
+
- if (asUnsupported != null) {
- Attribute[] usAttrs = asUnsupported.toArray();
+ saveAsPDF = new JButton(trans.get("printdlg.but.saveaspdf"));
+ saveAsPDF.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (onSavePDF()) {
+ PrintDialog.this.setVisible(false);
+ }
+ }
+ });
+ panel.add(saveAsPDF, "right, gap para");
+
- for (Attribute usAttr : usAttrs) {
- Class<? extends Attribute> category = usAttr.getCategory();
+ cancel = new JButton(trans.get("button.cancel"));
+ cancel.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ PrintDialog.this.setVisible(false);
+ }
+ });
+ panel.add(cancel, "right, gap para");
+
- if (ps.isAttributeCategorySupported(category)) {
- Attribute attr =
- (Attribute) ps.getDefaultAttributeValue(category);
+ expandAll(currentTree, true);
+ if (currentTree != noStagedTree) {
+ expandAll(noStagedTree, true);
+ }
+
- if (attr != null) {
- aset.add(attr);
- }
- else {
- aset.remove(category);
- }
- }
- else {
- aset.remove(category);
- }
- }
- }
- }
+ GUIUtil.setDisposableDialogOptions(this, previewButton);
+
+ }
+
+
+ @Override
+ public void valueChanged(final TreeSelectionEvent e) {
+ final TreePath path = e.getNewLeadSelectionPath();
+ if (path != null) {
+ previewButton.setEnabled(true);
+ saveAsPDF.setEnabled(true);
+ } else {
+ previewButton.setEnabled(false);
+ saveAsPDF.setEnabled(false);
+ }
+ }
+
+ /**
+ * If expand is true, expands all nodes in the tree. Otherwise, collapses all nodes in the theTree.
+ *
+ * @param theTree the tree to expand/contract
+ * @param expand expand if true, contract if not
+ */
+ public void expandAll(RocketPrintTree theTree, boolean expand) {
+ TreeNode root = (TreeNode) theTree.getModel().getRoot();
+ // Traverse theTree from root
+ expandAll(theTree, new TreePath(root), expand);
+ }
+
+ /**
+ * Recursively walk a tree, and if expand is true, expands all nodes in the tree. Otherwise, collapses all nodes in
+ * the theTree.
+ *
+ * @param theTree the tree to expand/contract
+ * @param parent the node to iterate/recurse over
+ * @param expand expand if true, contract if not
+ */
+ private void expandAll(RocketPrintTree theTree, TreePath parent, boolean expand) {
+ theTree.addSelectionPath(parent);
+ // Traverse children
+ TreeNode node = (TreeNode) parent.getLastPathComponent();
+ if (node.getChildCount() >= 0) {
+ for (Enumeration<?> e = node.children(); e.hasMoreElements();) {
+ TreeNode n = (TreeNode) e.nextElement();
+ TreePath path = parent.pathByAddingChild(n);
+ expandAll(theTree, path, expand);
+ }
+ }
+ // Expansion or collapse must be done bottom-up
+ if (expand) {
+ theTree.expandPath(parent);
+ } else {
+ theTree.collapsePath(parent);
+ }
+ }
+
+
+ /**
+ * Generate a report using a temporary file. The file will be deleted upon JVM exit.
+ *
+ * @param paper the name of the paper size
+ *
+ * @return a file, populated with the "printed" output (the rocket info)
+ *
+ * @throws IOException thrown if the file could not be generated
+ */
+ private File generateReport(PrintSettings settings) throws IOException {
+ final File f = File.createTempFile("openrocket-", ".pdf");
+ f.deleteOnExit();
+ return generateReport(f, settings);
+ }
+
+ /**
+ * Generate a report to a specified file.
+ *
+ * @param f the file to which rocket data will be written
+ * @param paper the name of the paper size
+ *
+ * @return a file, populated with the "printed" output (the rocket info)
+ *
+ * @throws IOException thrown if the file could not be generated
+ */
+ private File generateReport(File f, PrintSettings settings) throws IOException {
+ Iterator<PrintableContext> toBePrinted = currentTree.getToBePrinted();
+ new PrintController().print(document, toBePrinted, new FileOutputStream(f), settings);
+ return f;
+ }
+
+
+ /**
+ * Handler for when the Preview button is clicked.
+ */
+ private void onPreview() {
+ if (desktop != null) {
+ try {
+ PrintSettings settings = Prefs.getPrintSettings();
+ // TODO: HIGH: Remove UIManager, and pass settings to the actual printing methods
+ TemplateProperties.setColors(settings);
+ File f = generateReport(settings);
+ desktop.open(f);
+ } catch (IOException e) {
+ log.error("Could not open preview.", e);
+ JOptionPane.showMessageDialog(this, new String[] {
+ trans.get("error.preview.desc1"),
+ trans.get("error.preview.desc2") },
+ trans.get("error.preview.title"),
+ JOptionPane.ERROR_MESSAGE);
+ }
+ } else {
+ JOptionPane.showMessageDialog(this, new String[] {
+ trans.get("error.preview.desc1"),
+ trans.get("error.preview.desc2") },
+ trans.get("error.preview.title"),
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ }
+
+ /**
+ * Handler for when the "Save as PDF" button is clicked.
+ *
+ * @return true if the PDF was saved
+ */
+ 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() {
+
+ //Accept all directories and all pdf files.
+ @Override
+ public boolean accept(File f) {
+ if (f.isDirectory())
+ return true;
+ return f.getName().toLowerCase().endsWith(".pdf");
+ }
+
+ //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);
+ } catch (IOException e) {
+ ExceptionHandler.handleErrorCondition(e);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * 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 : "";
+ }
}
+++ /dev/null
-/*
- * PrintPanel.java
- */
-package net.sf.openrocket.gui.dialogs;
-
-import net.miginfocom.swing.MigLayout;
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.gui.components.ColorChooser;
-import net.sf.openrocket.gui.print.PrintController;
-import net.sf.openrocket.gui.print.PrintUtilities;
-import net.sf.openrocket.gui.print.PrintableContext;
-import net.sf.openrocket.gui.print.TemplateProperties;
-import net.sf.openrocket.gui.print.components.CheckTreeManager;
-import net.sf.openrocket.gui.print.components.RocketPrintTree;
-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 javax.print.attribute.PrintRequestAttributeSet;
-import javax.print.attribute.standard.MediaSizeName;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JColorChooser;
-import javax.swing.JComponent;
-import javax.swing.JDialog;
-import javax.swing.JFileChooser;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.UIManager;
-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 java.awt.Color;
-import java.awt.Desktop;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Enumeration;
-import java.util.Iterator;
-
-/**
- * This class isolates the Swing components used to create a panel that is added to the standard Java print dialog.
- */
-public class PrintPanel extends JPanel implements TreeSelectionListener {
-
- private static final LogHelper log = Application.getLogger();
- private static final Translator trans = Application.getTranslator();
-
- private final RocketPrintTree stagedTree;
- private final RocketPrintTree noStagedTree;
- private OpenRocketDocument rocDoc;
- private RocketPrintTree currentTree;
- private boolean bDesktopSupported = false;
- private Desktop desktop;
- private PrintDialog printDialog;
-
- JButton previewButton;
- JButton saveAsPDF;
-
- /**
- * Constructor.
- *
- * @param orDocument the OR rocket container
- * @param theParent the OR parent print dialog
- */
- public PrintPanel (OpenRocketDocument orDocument, PrintDialog theParent) {
-
- super(new MigLayout("fill, gap rel unrel"));
-
- // before any Desktop APIs are used, first check whether the API is
- // supported by this particular VM on this particular host
- if (Desktop.isDesktopSupported()) {
- bDesktopSupported = true;
- desktop = Desktop.getDesktop();
- }
-
- printDialog = theParent;
- rocDoc = orDocument;
- Rocket rocket = orDocument.getRocket();
-
- noStagedTree = RocketPrintTree.create(rocket.getName());
- noStagedTree.setShowsRootHandles(false);
- CheckTreeManager ctm = new net.sf.openrocket.gui.print.components.CheckTreeManager(noStagedTree);
- ctm.addTreeSelectionListener(this);
-
- final int stages = rocket.getStageCount();
-
- if (stages > 1) {
- stagedTree = RocketPrintTree.create(rocket.getName(), rocket.getChildren());
- ctm = new CheckTreeManager(stagedTree);
- stagedTree.setShowsRootHandles(false);
- ctm.addTreeSelectionListener(this);
- }
- else {
- stagedTree = noStagedTree;
- }
- currentTree = stagedTree;
-
- final JScrollPane scrollPane = new JScrollPane(stagedTree);
- add(scrollPane, "width 475!, wrap");
-
- final JCheckBox sortByStage = new JCheckBox("Show By Stage");
- sortByStage.setEnabled(stages > 1);
- sortByStage.setSelected(stages > 1);
- sortByStage.addActionListener(new ActionListener() {
- public void actionPerformed (ActionEvent e) {
- if (sortByStage.isEnabled()) {
- if (((JCheckBox) e.getSource()).isSelected()) {
- scrollPane.setViewportView(stagedTree);
- stagedTree.setExpandsSelectedPaths(true);
- currentTree = stagedTree;
- }
- else {
- scrollPane.setViewportView(noStagedTree);
- noStagedTree.setExpandsSelectedPaths(true);
- currentTree = noStagedTree;
- }
- }
- }
- });
- add(sortByStage, "wrap");
-
- //// Save as PDF button
- saveAsPDF = new JButton(trans.get("printdlg.but.saveaspdf"));
- saveAsPDF.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed (ActionEvent e) {
- onSavePDF(PrintPanel.this);
- }
- });
- add(saveAsPDF, "span 2, tag save");
-
- //// Preview button
- previewButton = new JButton(trans.get("printdlg.but.preview"));
- previewButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed (ActionEvent e) {
- onPreview();
- }
- });
- add(previewButton, "x 150");
-
- //// Settings button
- JButton settingsButton = new JButton(trans.get("printdlg.but.settings"));
- settingsButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed (ActionEvent e) {
- PrintSettingsDialog settingsDialog = new PrintSettingsDialog(printDialog.getDialog());
- settingsDialog.setVisible(true);
- }
- });
- add(settingsButton, "x 400");
-
- expandAll(currentTree, true);
- if (currentTree != noStagedTree) {
- expandAll(noStagedTree, true);
- }
- setVisible(true);
- }
-
- /**
- * The title of the tab that gets displayed for this panel, when placed in the print dialog.
- *
- * @return a title
- */
- public String getTitle () {
- return "Rocket";
- }
-
- @Override
- public void valueChanged (final TreeSelectionEvent e) {
- final TreePath path = e.getNewLeadSelectionPath();
- if (path != null){
- previewButton.setEnabled(true);
- saveAsPDF.setEnabled(true);
- }
- else {
- previewButton.setEnabled(false);
- saveAsPDF.setEnabled(false);
- }
- }
-
- /**
- * If expand is true, expands all nodes in the tree. Otherwise, collapses all nodes in the theTree.
- *
- * @param theTree the tree to expand/contract
- * @param expand expand if true, contract if not
- */
- public void expandAll (RocketPrintTree theTree, boolean expand) {
- TreeNode root = (TreeNode) theTree.getModel().getRoot();
- // Traverse theTree from root
- expandAll(theTree, new TreePath(root), expand);
- }
-
- /**
- * Recursively walk a tree, and if expand is true, expands all nodes in the tree. Otherwise, collapses all nodes in
- * the theTree.
- *
- * @param theTree the tree to expand/contract
- * @param parent the node to iterate/recurse over
- * @param expand expand if true, contract if not
- */
- private void expandAll (RocketPrintTree theTree, TreePath parent, boolean expand) {
- theTree.addSelectionPath(parent);
- // Traverse children
- TreeNode node = (TreeNode) parent.getLastPathComponent();
- if (node.getChildCount() >= 0) {
- for (Enumeration e = node.children(); e.hasMoreElements();) {
- TreeNode n = (TreeNode) e.nextElement();
- TreePath path = parent.pathByAddingChild(n);
- expandAll(theTree, path, expand);
- }
- }
- // Expansion or collapse must be done bottom-up
- if (expand) {
- theTree.expandPath(parent);
- }
- else {
- theTree.collapsePath(parent);
- }
- }
-
- /**
- * Get a media size name (the name of a paper size). If no page size is selected, it will default to the locale
- * specific page size (LETTER in North America, A4 elsewhere).
- *
- * @return the name of a page size
- */
- private MediaSizeName getMediaSize () {
- MediaSizeName paperSize = getMediaSize(printDialog.getAttributes());
- if (paperSize == null) {
- paperSize = PrintUtilities.getDefaultMedia().getMediaSizeName();
- }
- return paperSize;
- }
-
- /**
- * Get the media size name (the name of a paper size) as selected by the user.
- *
- * @param atts the set of selected printer attributes
- *
- * @return a media size name, may be null
- */
- private MediaSizeName getMediaSize (PrintRequestAttributeSet atts) {
- return (MediaSizeName) atts.get(javax.print.attribute.standard.Media.class);
- }
-
- /**
- * Generate a report using a temporary file. The file will be deleted upon JVM exit.
- *
- * @param paper the name of the paper size
- *
- * @return a file, populated with the "printed" output (the rocket info)
- *
- * @throws IOException thrown if the file could not be generated
- */
- private File generateReport (MediaSizeName paper) throws IOException {
- final File f = File.createTempFile("oro", ".pdf");
- f.deleteOnExit();
- return generateReport(f, paper);
- }
-
- /**
- * Generate a report to a specified file.
- *
- * @param f the file to which rocket data will be written
- * @param paper the name of the paper size
- *
- * @return a file, populated with the "printed" output (the rocket info)
- *
- * @throws IOException thrown if the file could not be generated
- */
- private File generateReport (File f, MediaSizeName paper) throws IOException {
- Iterator<PrintableContext> toBePrinted = currentTree.getToBePrinted();
- new PrintController().print(rocDoc, toBePrinted, new FileOutputStream(f), paper);
- return f;
- }
-
- /**
- * Generate a report to a byte array output stream.
- *
- * @return a stream populated with the "printed" output (the rocket info)
- */
- public ByteArrayOutputStream generateReport() {
- Iterator<PrintableContext> toBePrinted = currentTree.getToBePrinted();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- new PrintController().print(rocDoc, toBePrinted, baos, getMediaSize ());
- return baos;
- }
-
- /**
- * Handler for when the Preview button is clicked.
- */
- private void onPreview () {
- if (bDesktopSupported) {
- try {
- MediaSizeName paperSize = getMediaSize();
- File f = generateReport(paperSize);
- desktop.open(f);
- }
- catch (IOException e) {
- log.error("Could not create temporary file for previewing.", e);
- JOptionPane.showMessageDialog(this, "Could not create a temporary file for previewing.",
- "Error creating file", JOptionPane.ERROR_MESSAGE);
- }
- }
- else {
- JOptionPane.showMessageDialog(this,
- "Your environment does not support automatically opening the default PDF viewer.",
- "Error creating file", JOptionPane.INFORMATION_MESSAGE);
- }
- }
-
- /**
- * Handler for when the "Save as PDF" button is clicked.
- *
- * @param p the component to parent the save dialog to
- */
- private void onSavePDF (JComponent p) {
-
- 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() {
-
- //Accept all directories and all pdf files.
- public boolean accept (File f) {
- return true;
- }
-
- //The description of this filter
- public String getDescription () {
- return "pdf";
- }
- };
- chooser.setFileFilter(filter);
- int returnVal = chooser.showSaveDialog(p);
- if (returnVal == JFileChooser.APPROVE_OPTION) {
-
- try {
- String fname = chooser.getSelectedFile().getCanonicalPath();
- if (!getExtension(fname).equals("pdf")) {
- fname = fname + ".pdf";
- }
- File f = new File(fname);
- generateReport(f, getMediaSize());
- }
- catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
-
- /**
- * 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 : "";
- }
-}
-
-/**
- * This class is a dialog for displaying advanced settings for printing rocket related info.
- */
-class PrintSettingsDialog extends JDialog {
-
- /**
- * The fill color chooser.
- */
- private ColorChooser fill;
-
- /**
- * The line color chooser.
- */
- private ColorChooser line;
- private static final Translator trans = Application.getTranslator();
-
- /**
- * Construct a dialog for setting the advanced rocket print settings.
- *
- * @param parent the owning dialog
- */
- public PrintSettingsDialog (JDialog parent) {
-
- super(parent, "Advanced Settings", true);
- setLayout(new MigLayout("fill"));
-
- JPanel settingsPanel = new JPanel();
- settingsPanel.setLayout(new MigLayout("gap rel"));
-
- fill = addColorChooser(settingsPanel, "Template Fill", TemplateProperties.getFillColor());
- line = addColorChooser(settingsPanel, "Template Line", TemplateProperties.getLineColor());
-
- settingsPanel.add(fill);
- settingsPanel.add(line);
-
- add(settingsPanel, "wrap");
-
- //Close button
- JButton closeButton = new JButton(trans.get("dlg.but.close"));
- closeButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed (ActionEvent e) {
- UIManager.put(TemplateProperties.TEMPLATE_FILL_COLOR_PROPERTY, fill.getCurrentColor());
- UIManager.put(TemplateProperties.TEMPLATE_LINE_COLOR_PROPERTY, line.getCurrentColor());
- dispose();
- }
- });
- add(closeButton, "right, gapright para");
-
- setSize(400, 200);
- }
-
- /**
- * Add a color chooser to a panel.
- *
- * @param panel the parent panel to add the color chooser.
- * @param label the label that indicates which color property is being changed
- * @param initialColor the initial, or current, color to display
- *
- * @return a swing component containing a label, a colorized field, and a button that when clicked opens a color
- * chooser dialog
- */
- private ColorChooser addColorChooser (JPanel panel, String label, Color initialColor) {
- final JColorChooser colorChooser = new JColorChooser(initialColor);
- return new ColorChooser(panel, colorChooser, label);
- }
-
-}
--- /dev/null
+package net.sf.openrocket.gui.dialogs;
+
+import java.awt.Color;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.gui.adaptors.EnumModel;
+import net.sf.openrocket.gui.components.ColorChooserButton;
+import net.sf.openrocket.gui.print.PaperOrientation;
+import net.sf.openrocket.gui.print.PaperSize;
+import net.sf.openrocket.gui.print.PrintSettings;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.util.GUIUtil;
+
+/**
+ * This class is a dialog for displaying advanced settings for printing rocket related info.
+ */
+public class PrintSettingsDialog extends JDialog {
+ private static final LogHelper log = Application.getLogger();
+
+
+ /**
+ * Construct a dialog for setting the advanced rocket print settings.
+ *
+ * @param parent the owning dialog
+ */
+ public PrintSettingsDialog(Window parent, final PrintSettings settings) {
+ super(parent, "Print settings", ModalityType.APPLICATION_MODAL);
+
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+
+ panel.add(new JLabel("Template fill color:"));
+ final ColorChooserButton fillColorButton = new ColorChooserButton(settings.getTemplateFillColor());
+ fillColorButton.addColorPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ Color c = (Color) evt.getNewValue();
+ log.info("Template fill color changed to " + c);
+ settings.setTemplateFillColor(c);
+ }
+ });
+ panel.add(fillColorButton, "wrap para");
+
+
+ panel.add(new JLabel("Template border color:"));
+ final ColorChooserButton borderColorButton = new ColorChooserButton(settings.getTemplateBorderColor());
+ borderColorButton.addColorPropertyChangeListener(new PropertyChangeListener() {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ Color c = (Color) evt.getNewValue();
+ log.info("Template border color changed to " + c);
+ settings.setTemplateBorderColor(c);
+ }
+ });
+ panel.add(borderColorButton, "wrap para*2");
+
+
+
+ JComboBox combo = new JComboBox(new EnumModel<PaperSize>(settings, "PaperSize"));
+ panel.add(new JLabel("Paper size:"));
+ panel.add(combo, "growx, wrap para");
+
+
+ combo = new JComboBox(new EnumModel<PaperOrientation>(settings, "PaperOrientation"));
+ panel.add(new JLabel("Paper orientation:"));
+ panel.add(combo, "growx, wrap para*2");
+
+
+
+
+
+ JButton button = new JButton("Reset");
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ log.user("Resetting print setting values to defaults");
+ PrintSettings defaults = new PrintSettings();
+ settings.loadFrom(defaults);
+ fillColorButton.setSelectedColor(settings.getTemplateFillColor());
+ borderColorButton.setSelectedColor(settings.getTemplateBorderColor());
+ }
+ });
+ panel.add(button, "spanx, split, right");
+
+
+ JButton closeButton = new JButton("Close");
+ closeButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ PrintSettingsDialog.this.setVisible(false);
+ }
+ });
+ panel.add(closeButton, "right");
+
+ this.add(panel);
+ GUIUtil.setDisposableDialogOptions(this, closeButton);
+ }
+
+
+}
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
-import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
new SimpleFileFilter(trans.get("BasicFrame.SimpleFileFilter3"), ".rkt", ".rkt.gz");
-
+
public static final int COMPONENT_TAB = 0;
public static final int SIMULATION_TAB = 1;
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
+ log.user("Print action selected");
printAction();
}
});
log.info("Warnings while reading file: " + warnings);
WarningDialog.showWarnings(parent,
new Object[] {
- //// The following problems were encountered while opening
- trans.get("BasicFrame.WarningDialog.txt1") +" " + filename + ".",
+ //// The following problems were encountered while opening
+ trans.get("BasicFrame.WarningDialog.txt1") + " " + filename + ".",
//// Some design features may not have been loaded correctly.
- trans.get("BasicFrame.WarningDialog.txt2")
+ trans.get("BasicFrame.WarningDialog.txt2")
},
//// Warnings while opening file
trans.get("BasicFrame.WarningDialog.title"), warnings);
log.info("Confirming whether to save the design");
ComponentConfigDialog.hideDialog();
int result = JOptionPane.showConfirmDialog(this,
- trans.get("BasicFrame.dlg.lbl1") + rocket.getName() +
- trans.get("BasicFrame.dlg.lbl2")+ " " +
- trans.get("BasicFrame.dlg.lbl3"),
+ trans.get("BasicFrame.dlg.lbl1") + rocket.getName() +
+ trans.get("BasicFrame.dlg.lbl2") + " " +
+ trans.get("BasicFrame.dlg.lbl3"),
trans.get("BasicFrame.dlg.title"), JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (result == JOptionPane.YES_OPTION) {
*
*/
public void printAction() {
- new PrintDialog(document);
+ if (!Prefs.getBoolean("printing.experimental.communicated", false)) {
+ log.info("Showing printing is experimental warning to the user");
+ JOptionPane.showMessageDialog(this, "Printing is an currently an experimental feature " +
+ "and might not fully work on all platforms",
+ "Experimental feature", JOptionPane.WARNING_MESSAGE);
+ Prefs.putBoolean("printing.experimental.communicated", true);
+ }
+ new PrintDialog(this, document).setVisible(true);
}
/**
}
}
+
+ /*
+ * Detect and ignore bug 6933331 in Sun JRE 1.6.0_18 and others
+ */
+ if (t instanceof IllegalStateException) {
+ StackTraceElement[] trace = t.getStackTrace();
+
+ if (trace.length > 1 &&
+ trace[0].getClassName().equals("sun.awt.windows.WComponentPeer") &&
+ trace[0].getMethodName().equals("getBackBuffer")) {
+ log.warn("Ignoring Sun JRE bug 6933331 " +
+ "(see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6933331): " + t);
+ return true;
+ }
+ }
+
/*
* Detect and ignore bug in Sun JRE 1.6.0_19
*/
--- /dev/null
+package net.sf.openrocket.gui.print;
+
+import java.awt.Window;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+import net.miginfocom.swing.MigLayout;
+import net.sf.openrocket.util.GUIUtil;
+
+public class ConceptPrintDialog extends JDialog {
+
+ public ConceptPrintDialog() {
+ super((Window) null, "Print");
+
+ JPanel panel = new JPanel(new MigLayout("fill"));
+
+ JList list = new JList(new Object[] {
+ "Model name",
+ "Parts detail",
+ "Fin templates",
+ "Design report"
+ });
+ panel.add(new JScrollPane(list), "spanx, growx, wrap");
+
+ JCheckBox checkbox = new JCheckBox("Show by stage");
+ panel.add(checkbox, "");
+
+ JButton button = new JButton("Settings");
+ panel.add(button, "right, wrap para");
+
+ JLabel label = new JLabel("<html>Printer: LaserJet 6L<br>Paper size: A4 Portrait");
+ panel.add(label);
+
+ button = new JButton("Change");
+ panel.add(button, "right, wrap 20lp");
+
+ panel.add(new JButton("Save as PDF"), "split, spanx, right");
+ panel.add(new JButton("Preview"), "right");
+ panel.add(new JButton("Print"), "right");
+ panel.add(new JButton("Close"), "right");
+
+
+ this.add(panel);
+
+ }
+
+
+
+ public static void main(String[] args) throws InterruptedException, InvocationTargetException {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ GUIUtil.setBestLAF();
+ JDialog dialog = new ConceptPrintDialog();
+ GUIUtil.setDisposableDialogOptions(dialog, null);
+ dialog.setSize(450, 350);
+ dialog.setVisible(true);
+ }
+ });
+ }
+
+}
*/
package net.sf.openrocket.gui.print;
-import com.itextpdf.text.Document;
-import com.itextpdf.text.DocumentException;
-import com.itextpdf.text.Element;
-import com.itextpdf.text.Paragraph;
+import com.itextpdf.text.*;
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;
+import com.itextpdf.text.pdf.*;
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.print.visitor.BaseVisitorStrategy;
-import net.sf.openrocket.gui.print.visitor.MotorMountVisitorStrategy;
-import net.sf.openrocket.gui.print.visitor.StageVisitorStrategy;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.*;
import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Prefs;
-import java.awt.Graphics2D;
+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;
/**
* <p/>
* </pre>
*/
-public class DesignReport extends BaseVisitorStrategy {
+public class DesignReport {
+
+ /**
+ * The logger.
+ */
+ private static final LogHelper log = Application.getLogger();
/**
* The OR Document.
final RocketPanel panel;
/**
- * A stage visitor.
+ * The iText document.
*/
- private StageVisitorStrategy svs = new StageVisitorStrategy();
+ 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 theIDoc the iText document
*/
public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
- super(theIDoc, null);
+ document = theIDoc;
rocketDocument = theRocDoc;
panel = new RocketPanel(rocketDocument);
}
*
* @param writer a direct byte writer
*/
- public void print (PdfWriter writer) {
+ public void writeToDocument (PdfWriter writer) {
if (writer == null) {
return;
}
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");
+ PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
Rocket rocket = rocketDocument.getRocket();
final Configuration configuration = rocket.getDefaultConfiguration();
BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
}
catch (DocumentException e) {
- e.printStackTrace();
+ log.error("Could not set font.", e);
}
catch (IOException e) {
- e.printStackTrace();
+ log.error("Could not create font.", e);
}
int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
.toPoints(1)));
canvas.showText(rocketDocument.getRocket().getName());
- canvas.newlineShowText("Stages: ");
+ canvas.newlineShowText(STAGES);
canvas.showText("" + rocket.getStageCount());
if (configuration.hasMotors()) {
- canvas.newlineShowText("Mass (with motor" + ((configuration.getStageCount() > 1) ? "s): " : "): "));
+ if (configuration.getStageCount() > 1) {
+ canvas.newlineShowText(MASS_WITH_MOTORS);
+ }
+ else {
+ canvas.newlineShowText(MASS_WITH_MOTOR);
+ }
}
else {
- canvas.newlineShowText("Mass (Empty): ");
+ canvas.newlineShowText(MASS_EMPTY);
}
canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
- canvas.newlineShowText("Stability: ");
+ canvas.newlineShowText(STABILITY);
canvas.showText(text.getStability());
- canvas.newlineShowText("Cg: ");
+ canvas.newlineShowText(CG);
canvas.showText(text.getCg());
- canvas.newlineShowText("Cp: ");
+ canvas.newlineShowText(CP);
canvas.showText(text.getCp());
canvas.endText();
List<Double> stages = getStageWeights(rocket);
-
for (int j = 0; j < mids.length; j++) {
String mid = mids[j];
if (mid != null) {
- MotorMountVisitorStrategy mmvs = new MotorMountVisitorStrategy(document, mid);
- rocket.accept(new ComponentVisitor(mmvs));
+ final List<Motor> motorList = getMotorList(rocket, mid);
+
PdfPTable parent = new PdfPTable(2);
parent.setWidthPercentage(100);
parent.setHorizontalAlignment(Element.ALIGN_LEFT);
leading = 25;
}
addFlightData(rocket, mid, parent, leading);
- addMotorData(mmvs.getMotors(), parent, stages);
+ addMotorData(motorList, parent, stages);
document.add(parent);
}
}
}
catch (DocumentException e) {
- e.printStackTrace();
+ log.error("Could not modify document.", e);
+ }
+ }
+
+ /**
+ * Get the motor list for all motor mounts.
+ *
+ * @param theRocket the rocket object
+ * @param theMid the motor id
+ *
+ * @return a list of Motor
+ */
+ private List<Motor> getMotorList (final Rocket theRocket, final String theMid) {
+ Iterator<RocketComponent> components = theRocket.deepIterator();
+ final List<Motor> motorList = new ArrayList<Motor>();
+ while (components.hasNext()) {
+ RocketComponent rocketComponent = components.next();
+ if (rocketComponent instanceof MotorMount) {
+ MotorMount mm = (MotorMount) rocketComponent;
+ final Motor motor = mm.getMotor(theMid);
+ if (motor != null) {
+ motorList.add(motor);
+ }
+ }
}
+ return motorList;
}
/**
motorTable.setWidthPercentage(68);
motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
- final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM);
+ 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));
+ 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++) {
.UNITS_IMPULSE
.getDefaultUnit().toString(), border));
double ttw = motor.getAverageThrustEstimate() / (getStageWeight(stageWeights, i) + (motor
- .getLaunchCG().weight * 9.80665));
+ .getLaunchCG().weight * GRAVITY_CONSTANT));
motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
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();
+ Rocket duplicate = theRocket.copyWithOriginalID();
Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
simulation.getConditions().setMotorConfigurationID(mid);
DecimalFormat df = new DecimalFormat("#,##0.0#");
- final PdfPCell cell = ITextHelper.createCell("Altitude", 2, 2);
+ 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(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(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(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(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(LANDING_VELOCITY, 2, 2));
labelTable.addCell(ITextHelper.createCell(df.format(
flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
parent.addCell(c);
}
catch (DocumentException e) {
- e.printStackTrace();
+ log.error("Could not add flight data to document.", e);
}
}
}
}
/**
- * Use a visitor to get the sorted list of Stage references, then from those get the stage masses and convert to
- * weight.
+ * From a list of Stages get the stage masses and convert to weight.
*
* @param rocket the rocket
*
* @return a sorted list of Stage weights (mass * gravity), in Newtons
*/
private List<Double> getStageWeights (Rocket rocket) {
- rocket.accept(new ComponentVisitor(svs));
- svs.close();
- List<Double> stages = svs.getStages();
+ List<Double> stages = getStageMasses(rocket);
+
for (int i = 0; i < stages.size(); i++) {
Double stage = stages.get(i);
- stages.set(i, stage * 9.80665);
+ stages.set(i, stage * GRAVITY_CONSTANT);
+ }
+ return stages;
+ }
+
+ /**
+ * From a list of Stages get the stage masses.
+ *
+ * @param rocket the rocket
+ *
+ * @return a sorted list of Stage masses
+ */
+ private List<Double> getStageMasses (final Rocket rocket) {
+ Double mass = 0d;
+
+ List<Double> stages = new ArrayList<Double>();
+ Iterator<RocketComponent> iter = rocket.deepIterator();
+ while (iter.hasNext()) {
+ RocketComponent rocketComponent = iter.next();
+ if (rocketComponent instanceof Stage) {
+ if (mass > 0d) {
+ stages.add(mass);
+ mass = 0d;
+ }
+ }
+ else {
+ mass += rocketComponent.getMass();
+ }
+ }
+ if (mass > 0d) {
+ stages.add(mass);
}
return stages;
}
import javax.print.DocFlavor;
import javax.print.attribute.AttributeSetUtilities;
import javax.print.attribute.DocAttributeSet;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
+import java.io.*;
/**
+ * This class implements a javax Doc specifically for PDF printing. All reports in OpenRocket are PDF (iText) based.
*/
public class PDFPrintStreamDoc implements Doc {
+ /** The source stream of the PDF document. */
private InputStream stream;
+
+ /** The document's attributes. */
private DocAttributeSet attributeSet;
+ /**
+ * Constructor.
+ *
+ * @param ostream an output stream representing the pdf doc
+ * @param attributes the attributes of the document
+ */
public PDFPrintStreamDoc (ByteArrayOutputStream ostream, DocAttributeSet attributes) {
stream = new ByteArrayInputStream(ostream.toByteArray());
if (attributes != null) {
}
}
+ /**
+ * Flavor is PDF.
+ *
+ * @return PDF flavor
+ */
+ @Override
public DocFlavor getDocFlavor () {
return DocFlavor.INPUT_STREAM.PDF;
}
+ @Override
public DocAttributeSet getAttributes () {
return attributeSet;
}
/* Since the data is to be supplied as an InputStream delegate to
* getStreamForBytes().
*/
-
+ @Override
public Object getPrintData () throws IOException {
return getStreamForBytes();
}
+ /**
+ * Intentionally null since the flavor is PDF.
+ *
+ * @return null
+ */
+ @Override
public Reader getReaderForText () {
return null;
}
/* Return the print data as an InputStream.
* Always return the same instance.
*/
-
+ @Override
public InputStream getStreamForBytes () throws IOException {
return stream;
}
--- /dev/null
+package net.sf.openrocket.gui.print;
+
+import com.itextpdf.text.Rectangle;
+import com.itextpdf.text.RectangleReadOnly;
+
+public enum PaperOrientation {
+
+ PORTRAIT("Portrait") {
+ @Override
+ public Rectangle orient(Rectangle rect) {
+ return new RectangleReadOnly(rect);
+ }
+ },
+ LANDSCAPE("Landscape") {
+ @Override
+ public Rectangle orient(Rectangle rect) {
+ return new RectangleReadOnly(new Rectangle(rect).rotate());
+ }
+ };
+
+
+ private final String name;
+
+ private PaperOrientation(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Change the orientation of a portrait paper to the orientation represented by this
+ * orientation.
+ *
+ * @param rect the original paper size rectangle
+ * @return the oriented paper size rectangle
+ */
+ public abstract Rectangle orient(Rectangle rect);
+
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
-/*
- * PaperSize.java
- */
package net.sf.openrocket.gui.print;
-import com.itextpdf.text.PageSize;
-import com.itextpdf.text.Rectangle;
-import com.itextpdf.text.RectangleReadOnly;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Locale;
-import javax.print.attribute.standard.MediaSizeName;
-import java.util.HashMap;
-import java.util.Map;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
-/**
- * Various mappings of paper sizes.
- */
-public class PaperSize {
-
- private static Map<String, MediaSizeName> paperNames = new HashMap<String, MediaSizeName>();
- private static Map<MediaSizeName, Rectangle> paperItext = new HashMap<MediaSizeName, Rectangle>();
-
- static {
- populateNameMap();
- populateITextSizeMap();
- }
- private PaperSize() {}
-
- public static MediaSizeName convert(String name) {
- return paperNames.get(name);
- }
+import com.itextpdf.text.PageSize;
+import com.itextpdf.text.Rectangle;
- public static Rectangle convert(MediaSizeName name) {
- return paperItext.get(name);
- }
-
- private static void populateNameMap() {
- paperNames.put("iso-a0", MediaSizeName.ISO_A0);
- paperNames.put("iso-a1", MediaSizeName.ISO_A1);
- paperNames.put("iso-a2", MediaSizeName.ISO_A2);
- paperNames.put("iso-a3", MediaSizeName.ISO_A3);
- paperNames.put("iso-a4", MediaSizeName.ISO_A4);
- paperNames.put("iso-a5", MediaSizeName.ISO_A5);
- paperNames.put("iso-a6", MediaSizeName.ISO_A6);
- paperNames.put("iso-a7", MediaSizeName.ISO_A7);
- paperNames.put("iso-a8", MediaSizeName.ISO_A8);
- paperNames.put("iso-a9", MediaSizeName.ISO_A9);
- paperNames.put("iso-a10", MediaSizeName.ISO_A10);
- paperNames.put("iso-b0", MediaSizeName.ISO_B0);
- paperNames.put("iso-b1", MediaSizeName.ISO_B1);
- paperNames.put("iso-b2", MediaSizeName.ISO_B2);
- paperNames.put("iso-b3", MediaSizeName.ISO_B3);
- paperNames.put("iso-b4", MediaSizeName.ISO_B4);
- paperNames.put("iso-b5", MediaSizeName.ISO_B5);
- paperNames.put("iso-b6", MediaSizeName.ISO_B6);
- paperNames.put("iso-b7", MediaSizeName.ISO_B7);
- paperNames.put("iso-b8", MediaSizeName.ISO_B8);
- paperNames.put("iso-b9", MediaSizeName.ISO_B9);
- paperNames.put("iso-b10", MediaSizeName.ISO_B10);
- paperNames.put("na-letter", MediaSizeName.NA_LETTER);
- paperNames.put("na-legal", MediaSizeName.NA_LEGAL);
- paperNames.put("na-8x10", MediaSizeName.NA_8X10);
- paperNames.put("na-5x7", MediaSizeName.NA_5X7);
- paperNames.put("executive", MediaSizeName.EXECUTIVE);
- paperNames.put("folio", MediaSizeName.FOLIO);
- paperNames.put("invoice", MediaSizeName.INVOICE);
- paperNames.put("tabloid", MediaSizeName.TABLOID);
- paperNames.put("ledger", MediaSizeName.LEDGER);
- paperNames.put("quarto", MediaSizeName.QUARTO);
- paperNames.put("iso-c0", MediaSizeName.ISO_C0);
- paperNames.put("iso-c1", MediaSizeName.ISO_C1);
- paperNames.put("iso-c2", MediaSizeName.ISO_C2);
- paperNames.put("iso-c3", MediaSizeName.ISO_C3);
- paperNames.put("iso-c4", MediaSizeName.ISO_C4);
- paperNames.put("iso-c5", MediaSizeName.ISO_C5);
- paperNames.put("iso-c6", MediaSizeName.ISO_C6);
- paperNames.put("iso-designated-long", MediaSizeName.ISO_DESIGNATED_LONG);
- paperNames.put("jis-b0", MediaSizeName.JIS_B0);
- paperNames.put("jis-b1", MediaSizeName.JIS_B1);
- paperNames.put("jis-b2", MediaSizeName.JIS_B2);
- paperNames.put("jis-b3", MediaSizeName.JIS_B3);
- paperNames.put("jis-b4", MediaSizeName.JIS_B4);
- paperNames.put("jis-b5", MediaSizeName.JIS_B5);
- paperNames.put("jis-b6", MediaSizeName.JIS_B6);
- paperNames.put("jis-b7", MediaSizeName.JIS_B7);
- paperNames.put("jis-b8", MediaSizeName.JIS_B8);
- paperNames.put("jis-b9", MediaSizeName.JIS_B9);
- paperNames.put("jis-b10", MediaSizeName.JIS_B10);
- paperNames.put("a", MediaSizeName.A);
- paperNames.put("b", MediaSizeName.B);
- paperNames.put("c", MediaSizeName.C);
- paperNames.put("d", MediaSizeName.D);
- paperNames.put("e", MediaSizeName.E);
- }
+public enum PaperSize {
+ A3("A3", PageSize.A3),
+ A4("A4", PageSize.A4),
+ A5("A5", PageSize.A5),
+ LETTER("Letter", PageSize.LETTER),
+ LEGAL("Legal", PageSize.LEGAL);
+
+ private final String name;
+ private final Rectangle size;
+
+ private PaperSize(String name, Rectangle size) {
+ this.name = name;
+ this.size = size;
+ }
+
+ public Rectangle getSize() {
+ return size;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+
- private static void populateITextSizeMap() {
- paperItext.put(MediaSizeName.ISO_A0, PageSize.A0);
- paperItext.put(MediaSizeName.ISO_A1, PageSize.A1);
- paperItext.put(MediaSizeName.ISO_A2, PageSize.A2);
- paperItext.put(MediaSizeName.ISO_A3, PageSize.A3);
- paperItext.put(MediaSizeName.ISO_A4, PageSize.A4);
- paperItext.put(MediaSizeName.ISO_A5, PageSize.A5);
- paperItext.put(MediaSizeName.ISO_A6, PageSize.A6);
- paperItext.put(MediaSizeName.ISO_A7, PageSize.A7);
- paperItext.put(MediaSizeName.ISO_A8, PageSize.A8);
- paperItext.put(MediaSizeName.ISO_A9, PageSize.A9);
- paperItext.put(MediaSizeName.ISO_A10, PageSize.A10);
- paperItext.put(MediaSizeName.ISO_B0, PageSize.B0);
- paperItext.put(MediaSizeName.ISO_B1, PageSize.B1);
- paperItext.put(MediaSizeName.ISO_B2, PageSize.B2);
- paperItext.put(MediaSizeName.ISO_B3, PageSize.B3);
- paperItext.put(MediaSizeName.ISO_B4, PageSize.B4);
- paperItext.put(MediaSizeName.ISO_B5, PageSize.B5);
- paperItext.put(MediaSizeName.ISO_B6, PageSize.B6);
- paperItext.put(MediaSizeName.ISO_B7, PageSize.B7);
- paperItext.put(MediaSizeName.ISO_B8, PageSize.B8);
- paperItext.put(MediaSizeName.ISO_B9, PageSize.B9);
- paperItext.put(MediaSizeName.ISO_B10, PageSize.B10);
- paperItext.put(MediaSizeName.NA_LETTER, PageSize.LETTER);
- paperItext.put(MediaSizeName.NA_LEGAL, PageSize.LEGAL);
- paperItext.put(MediaSizeName.EXECUTIVE, PageSize.EXECUTIVE);
- paperItext.put(MediaSizeName.A, PageSize.LETTER);
- paperItext.put(MediaSizeName.B, PageSize._11X17);
- paperItext.put(MediaSizeName.C, new RectangleReadOnly(PrintUnit.INCHES.toPoints(17), PrintUnit.INCHES.toPoints(22)));
- paperItext.put(MediaSizeName.D, new RectangleReadOnly(PrintUnit.INCHES.toPoints(22), PrintUnit.INCHES.toPoints(34)));
- paperItext.put(MediaSizeName.E, new RectangleReadOnly(PrintUnit.INCHES.toPoints(34), PrintUnit.INCHES.toPoints(44)));
- }
-
+ //////////////////////////
+
+ private static final LogHelper log = Application.getLogger();
+ private static PaperSize defaultSize = null;
+
+ /**
+ * Return the default paper size for the current system.
+ * @return the default paper size
+ */
+ public static PaperSize getDefault() {
+ if (defaultSize == null) {
+
+ // Test environment variable "PAPERSIZE" (Unix)
+ defaultSize = getDefaultFromEnvironmentVariable();
+ if (defaultSize != null) {
+ log.info("Selecting default paper size from PAPERSIZE environment variable: " + defaultSize);
+ return defaultSize;
+ }
+
+ // Test /etc/papersize (Unix)
+ defaultSize = getDefaultFromEtcPapersize();
+ if (defaultSize != null) {
+ log.info("Selecting default paper size from /etc/papersize: " + defaultSize);
+ return defaultSize;
+ }
+
+ // Test user.country
+ defaultSize = getDefaultForCountry(System.getProperty("user.country"));
+ if (defaultSize != null) {
+ log.info("Selecting default paper size based on user.country: " + defaultSize);
+ return defaultSize;
+ }
+
+ // Test locale country
+ defaultSize = getDefaultForCountry(Locale.getDefault().getCountry());
+ if (defaultSize != null) {
+ log.info("Selecting default paper size based on locale country: " + defaultSize);
+ return defaultSize;
+ }
+
+ // Fallback to A4
+ defaultSize = A4;
+ log.info("Selecting default paper size fallback: " + defaultSize);
+ }
+
+ return defaultSize;
+ }
+
+
+ /**
+ * Attempt to read the default paper size from the "PAPERSIZE" environment variable.
+ *
+ * @return the default paper size if successful, or <code>null</code> if unable to read/parse file.
+ */
+ private static PaperSize getDefaultFromEnvironmentVariable() {
+ String str = System.getenv("PAPERSIZE");
+ return getSizeFromString(str);
+ }
+
+ /**
+ * Attempt to read the default paper size from the file defined by the environment variable
+ * PAPERCONF or from /etc/papersize.
+ *
+ * @return the default paper size if successful, or <code>null</code> if unable to read/parse file.
+ */
+ private static PaperSize getDefaultFromEtcPapersize() {
+
+ // Find file to read
+ String file = System.getenv("PAPERCONF");
+ if (file == null) {
+ file = "/etc/papersize";
+ }
+
+ // Attempt to read the file
+ BufferedReader in = null;
+ try {
+
+ String str;
+ in = new BufferedReader(new FileReader(file));
+ while ((str = in.readLine()) != null) {
+ if (str.matches("^\\s*(#.*|$)")) {
+ continue;
+ }
+ break;
+ }
+
+ return getSizeFromString(str);
+
+ } catch (IOException e) {
+
+ // Could not read file
+ return null;
+
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Get a paper size based on a string. The string is trimmed and case-insensitively
+ * compared to the base names of the paper sizes.
+ *
+ * @param size the size string (may be null)
+ * @return the corresponding paper size, or null if unknown
+ */
+ static PaperSize getSizeFromString(String size) {
+ if (size == null) {
+ return null;
+ }
+
+ size = size.trim();
+ for (PaperSize p : PaperSize.values()) {
+ if (p.name.equalsIgnoreCase(size)) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Get default paper size for a specific country. This method falls back to A4 for
+ * any country not known to use Letter.
+ *
+ * @param country the 2-char country code (may be null)
+ * @return the paper size, or <code>null</code> if country is not a country code
+ */
+ static PaperSize getDefaultForCountry(String country) {
+ /*
+ * List is based on info from http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/territory_language_information.html
+ * OpenOffice.org agrees with this: http://wiki.services.openoffice.org/wiki/DefaultPaperSize#Summary
+ */
+ final String[] letterCountries = { "BZ", "CA", "CL", "CO", "CR", "SV", "GT", "MX", "NI", "PA", "PH", "PR", "US", "VE" };
+
+ if (country == null || !country.matches("^[a-zA-Z][a-zA-Z]$")) {
+ return null;
+ }
+
+ country = country.toUpperCase();
+ for (String c : letterCountries) {
+ if (c.equals(country)) {
+ return LETTER;
+ }
+ }
+ return A4;
+ }
+
}
*/
package net.sf.openrocket.gui.print;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import java.util.Set;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.gui.print.visitor.FinSetVisitorStrategy;
+import net.sf.openrocket.gui.print.visitor.PartsDetailVisitorStrategy;
+
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.pdf.PdfBoolean;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfWriter;
-import net.sf.openrocket.document.OpenRocketDocument;
-import net.sf.openrocket.gui.print.visitor.FinSetVisitorStrategy;
-import net.sf.openrocket.gui.print.visitor.PartsDetailVisitorStrategy;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-
-import javax.print.attribute.standard.MediaSizeName;
-import java.io.OutputStream;
-import java.util.Iterator;
-import java.util.Set;
/**
* This is the main active object for printing. It performs all actions necessary to create and populate the print
* file.
*/
public class PrintController {
-
- /**
- * Print the selected components to a PDF document.
- *
- * @param doc the OR document
- * @param toBePrinted the user chosen items to print
- * @param outputFile the file being written to
- * @param msn the paper size
- */
- public void print (OpenRocketDocument doc, Iterator<PrintableContext> toBePrinted, OutputStream outputFile, MediaSizeName msn) {
-
- Document idoc = new Document(convertWithDefault(msn));
- PdfWriter writer = null;
- try {
- writer = PdfWriter.getInstance(idoc, outputFile);
- writer.setStrictImageSequence(true);
-
- writer.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);
- writer.addViewerPreference(PdfName.PICKTRAYBYPDFSIZE, PdfBoolean.PDFTRUE);
- try {
- idoc.open();
- Thread.sleep(1000);
- }
- catch (InterruptedException e) {
- }
- while (toBePrinted.hasNext()) {
- PrintableContext printableContext = toBePrinted.next();
-
- Set<Integer> stages = printableContext.getStageNumber();
-
- switch (printableContext.getPrintable()) {
- case DESIGN_REPORT:
- DesignReport dp = new DesignReport(doc, idoc);
- dp.print(writer);
- idoc.newPage();
- break;
- case FIN_TEMPLATE:
- final ComponentVisitor finVisitor = new ComponentVisitor(new FinSetVisitorStrategy(idoc,
- writer,
- stages));
- finVisitor.visit(doc.getRocket());
- finVisitor.close();
- break;
- case PARTS_DETAIL:
- final ComponentVisitor detailVisitor = new ComponentVisitor(new PartsDetailVisitorStrategy(idoc,
- writer,
- stages));
- detailVisitor.visit(doc.getRocket());
- detailVisitor.close();
- idoc.newPage();
- break;
- /* case PARTS_LIST:
- final ComponentVisitor partsVisitor = new ComponentVisitor(new PartsListVisitorStrategy(idoc,
- writer,
- stages));
- partsVisitor.visit(doc.getRocket());
- partsVisitor.close();
- idoc.newPage();
- break;
- */
- }
- }
- //Stupid iText throws a really nasty exception if there is no data when close is called.
- if (writer.getCurrentDocumentSize() <= 140) {
- writer.setPageEmpty(false);
- }
- writer.close();
- idoc.close();
- }
- catch (DocumentException e) {
- }
- catch (ExceptionConverter ec) {
- }
-/* finally {
- if (fileOutputStream != null) {
- try {
- fileOutputStream.close();
- }
- catch (IOException e) {
- }
- }
- }
- */
- }
-
- private Rectangle convertWithDefault (final MediaSizeName msn) {
- Rectangle result = PaperSize.convert(msn);
- if (result == null) {
- result = PaperSize.convert(PrintUtilities.getDefaultMedia().getMediaSizeName());
- }
- return result;
- }
-
+
+ /**
+ * Print the selected components to a PDF document.
+ *
+ * @param doc the OR document
+ * @param toBePrinted the user chosen items to print
+ * @param outputFile the file being written to
+ * @param msn the paper size
+ */
+ public void print(OpenRocketDocument doc, Iterator<PrintableContext> toBePrinted, OutputStream outputFile,
+ PrintSettings settings) {
+
+ Document idoc = new Document(getSize(settings));
+ PdfWriter writer = null;
+ try {
+ writer = PdfWriter.getInstance(idoc, outputFile);
+ writer.setStrictImageSequence(true);
+
+ writer.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);
+ writer.addViewerPreference(PdfName.PICKTRAYBYPDFSIZE, PdfBoolean.PDFTRUE);
+ try {
+ idoc.open();
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ }
+ while (toBePrinted.hasNext()) {
+ PrintableContext printableContext = toBePrinted.next();
+
+ Set<Integer> stages = printableContext.getStageNumber();
+
+ switch (printableContext.getPrintable()) {
+ case DESIGN_REPORT:
+ DesignReport dp = new DesignReport(doc, idoc);
+ dp.writeToDocument(writer);
+ idoc.newPage();
+ break;
+ case FIN_TEMPLATE:
+ final FinSetVisitorStrategy finWriter = new FinSetVisitorStrategy(idoc,
+ writer,
+ stages);
+ finWriter.writeToDocument(doc.getRocket());
+ break;
+ case PARTS_DETAIL:
+ final PartsDetailVisitorStrategy detailVisitor = new PartsDetailVisitorStrategy(idoc,
+ writer,
+ stages);
+ detailVisitor.writeToDocument(doc.getRocket());
+ detailVisitor.close();
+ idoc.newPage();
+ break;
+ }
+ }
+ //Stupid iText throws a really nasty exception if there is no data when close is called.
+ if (writer.getCurrentDocumentSize() <= 140) {
+ writer.setPageEmpty(false);
+ }
+ writer.close();
+ idoc.close();
+ } catch (DocumentException e) {
+ } catch (ExceptionConverter ec) {
+ } finally {
+ if (outputFile != null) {
+ try {
+ outputFile.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the correct paper size from the print settings.
+ *
+ * @param settings the print settings
+ * @return the paper size
+ */
+ private Rectangle getSize(PrintSettings settings) {
+ PaperSize size = settings.getPaperSize();
+ PaperOrientation orientation = settings.getPaperOrientation();
+ return orientation.orient(size.getSize());
+ }
+
}
--- /dev/null
+package net.sf.openrocket.gui.print;
+
+import java.awt.Color;
+
+import net.sf.openrocket.util.AbstractChangeSource;
+
+/**
+ * A class containing all printing settings.
+ */
+public class PrintSettings extends AbstractChangeSource {
+
+ private Color templateFillColor = Color.LIGHT_GRAY;
+ private Color templateBorderColor = Color.DARK_GRAY;
+
+ private PaperSize paperSize = PaperSize.getDefault();
+ private PaperOrientation paperOrientation = PaperOrientation.PORTRAIT;
+
+
+ public Color getTemplateFillColor() {
+ return templateFillColor;
+ }
+
+ public void setTemplateFillColor(Color templateFillColor) {
+ // Implicitly tests against setting null
+ if (templateFillColor.equals(this.templateFillColor)) {
+ return;
+ }
+ this.templateFillColor = templateFillColor;
+ fireChangeEvent();
+ }
+
+ public Color getTemplateBorderColor() {
+ return templateBorderColor;
+ }
+
+ public void setTemplateBorderColor(Color templateBorderColor) {
+ // Implicitly tests against setting null
+ if (templateBorderColor.equals(this.templateBorderColor)) {
+ return;
+ }
+ this.templateBorderColor = templateBorderColor;
+ fireChangeEvent();
+ }
+
+ public PaperSize getPaperSize() {
+ return paperSize;
+ }
+
+ public void setPaperSize(PaperSize paperSize) {
+ if (paperSize.equals(this.paperSize)) {
+ return;
+ }
+ this.paperSize = paperSize;
+ fireChangeEvent();
+ }
+
+ public PaperOrientation getPaperOrientation() {
+ return paperOrientation;
+ }
+
+ public void setPaperOrientation(PaperOrientation orientation) {
+ if (orientation.equals(paperOrientation)) {
+ return;
+ }
+ this.paperOrientation = orientation;
+ fireChangeEvent();
+ }
+
+
+
+ /**
+ * Load settings from the specified print settings.
+ * @param settings the settings to load
+ */
+ public void loadFrom(PrintSettings settings) {
+ this.templateFillColor = settings.templateFillColor;
+ this.templateBorderColor = settings.templateBorderColor;
+ this.paperSize = settings.paperSize;
+ this.paperOrientation = settings.paperOrientation;
+ fireChangeEvent();
+ }
+
+
+ @Override
+ public String toString() {
+ return "PrintSettings [templateFillColor=" + templateFillColor + ", templateBorderColor=" + templateBorderColor + ", paperSize=" + paperSize + ", paperOrientation=" + paperOrientation + "]";
+ }
+
+}
package net.sf.openrocket.gui.print;
-import com.itextpdf.text.Chunk;
-import com.itextpdf.text.Document;
-import com.itextpdf.text.DocumentException;
-import com.itextpdf.text.Font;
-import com.itextpdf.text.Paragraph;
-
-import javax.print.DocFlavor;
-import javax.print.PrintService;
-import javax.print.PrintServiceLookup;
-import javax.print.ServiceUI;
-import javax.print.attribute.HashPrintRequestAttributeSet;
-import javax.print.attribute.PrintRequestAttributeSet;
-import javax.print.attribute.standard.MediaSize;
-import javax.swing.RepaintManager;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
-import java.awt.print.PrinterException;
-import java.awt.print.PrinterJob;
-import java.util.Locale;
-
-public class PrintUtilities implements Printable {
-
- public static final int NORMAL_FONT_SIZE = Font.DEFAULTSIZE - 3;
- public static final int SMALL_FONT_SIZE = NORMAL_FONT_SIZE - 3;
-
- public static final Font BOLD = new Font(Font.FontFamily.HELVETICA, NORMAL_FONT_SIZE, Font.BOLD);
- public static final Font BIG_BOLD = new Font(Font.FontFamily.HELVETICA, NORMAL_FONT_SIZE + 3, Font.BOLD);
- public static final Font BOLD_UNDERLINED = new Font(Font.FontFamily.HELVETICA, NORMAL_FONT_SIZE,
- Font.BOLD | Font.UNDERLINE);
- public static final Font NORMAL = new Font(Font.FontFamily.HELVETICA, NORMAL_FONT_SIZE);
- public static final Font SMALL = new Font(Font.FontFamily.HELVETICA, SMALL_FONT_SIZE);
-
-
- private Component componentToBePrinted;
-
- public static void printComponent (Component c) {
- new PrintUtilities(c).print();
- }
-
- public PrintUtilities (Component componentToBePrinted) {
- this.componentToBePrinted = componentToBePrinted;
- }
- public void print () {
- PrinterJob printJob = PrinterJob.getPrinterJob();
- printJob.setPrintable(this);
- if (printJob.printDialog()) {
- try {
- printJob.print();
- }
- catch (PrinterException pe) {
- System.out.println("Error printing: " + pe);
- }
- }
- }
-
- public int print (Graphics g, PageFormat pageFormat, int pageIndex) {
- if (pageIndex > 0) {
- return (NO_SUCH_PAGE);
- }
- else {
- Graphics2D g2d = (Graphics2D) g;
- translateToJavaOrigin(g2d, pageFormat);
- disableDoubleBuffering(componentToBePrinted);
- componentToBePrinted.paint(g2d);
- enableDoubleBuffering(componentToBePrinted);
- return (PAGE_EXISTS);
- }
- }
-
- public static void disableDoubleBuffering (Component c) {
- RepaintManager currentManager = RepaintManager.currentManager(c);
- currentManager.setDoubleBufferingEnabled(false);
- }
-
- public static void enableDoubleBuffering (Component c) {
- RepaintManager currentManager = RepaintManager.currentManager(c);
- currentManager.setDoubleBufferingEnabled(true);
- }
-
- public static PrintService askUserForPDFPrintService () {
- return askUserForPrintService(DocFlavor.INPUT_STREAM.PDF);
- }
-
- public static PrintService askUserForPrintService (DocFlavor flavor) {
- PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
- PrintService svc = PrintServiceLookup.lookupDefaultPrintService();
- PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
- attrs.add(getDefaultMedia().getMediaSizeName());
-
- return ServiceUI.printDialog(null, 100, 100, services, svc, flavor, attrs);
- }
+import javax.swing.RepaintManager;
- /**
- * Sets the paper size for pages using these attributes to the default size for the default locale. The default size
- * for locales in the United States and Canada is MediaType.NA_LETTER. The default size for all other locales is
- * MediaType.ISO_A4.
- */
- public static MediaSize getDefaultMedia () {
- String defaultCountry = Locale.getDefault().getCountry();
- if (defaultCountry != null &&
- (defaultCountry.equals(Locale.US.getCountry()) ||
- defaultCountry.equals(Locale.CANADA.getCountry()))) {
- return MediaSize.NA.LETTER;
- }
- else {
- return MediaSize.ISO.A4;
- }
- }
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
- /**
- * Translate the page format coordinates onto the graphics object using Java's origin (top left).
- *
- * @param g2d the graphics object
- * @param pageFormat the print page format
- */
- public static void translateToJavaOrigin (Graphics2D g2d, PageFormat pageFormat) {
- g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
- }
+import com.itextpdf.text.Chunk;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.Font;
+import com.itextpdf.text.Paragraph;
- public static void addText (Document document, com.itextpdf.text.Font font, String title) {
- Chunk sectionHeader = new Chunk(title);
- sectionHeader.setFont(font);
- try {
- Paragraph p = new Paragraph();
- p.add(sectionHeader);
- document.add(p);
- }
- catch (DocumentException e) {
- e.printStackTrace();
- }
- }
+/**
+ * Utilities methods and fonts used for printing.
+ */
+public class PrintUtilities implements Printable {
+
+ /**
+ * The logger.
+ */
+ private static final LogHelper log = Application.getLogger();
+
+ public static final int NORMAL_FONT_SIZE = Font.DEFAULTSIZE - 3;
+ public static final int SMALL_FONT_SIZE = NORMAL_FONT_SIZE - 3;
+
+ public static final Font BOLD = new Font(Font.FontFamily.HELVETICA, NORMAL_FONT_SIZE, Font.BOLD);
+ public static final Font BIG_BOLD = new Font(Font.FontFamily.HELVETICA, NORMAL_FONT_SIZE + 3, Font.BOLD);
+ public static final Font BOLD_UNDERLINED = new Font(Font.FontFamily.HELVETICA, NORMAL_FONT_SIZE,
+ Font.BOLD | Font.UNDERLINE);
+ public static final Font NORMAL = new Font(Font.FontFamily.HELVETICA, NORMAL_FONT_SIZE);
+ public static final Font SMALL = new Font(Font.FontFamily.HELVETICA, SMALL_FONT_SIZE);
+
+
+ private Component componentToBePrinted;
+
+ public PrintUtilities(Component componentToBePrinted) {
+ this.componentToBePrinted = componentToBePrinted;
+ }
+
+ @Override
+ public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
+ if (pageIndex > 0) {
+ return (NO_SUCH_PAGE);
+ } else {
+ Graphics2D g2d = (Graphics2D) g;
+ translateToJavaOrigin(g2d, pageFormat);
+ disableDoubleBuffering(componentToBePrinted);
+ componentToBePrinted.paint(g2d);
+ enableDoubleBuffering(componentToBePrinted);
+ return (PAGE_EXISTS);
+ }
+ }
+
+ public static void disableDoubleBuffering(Component c) {
+ RepaintManager currentManager = RepaintManager.currentManager(c);
+ currentManager.setDoubleBufferingEnabled(false);
+ }
+
+ public static void enableDoubleBuffering(Component c) {
+ RepaintManager currentManager = RepaintManager.currentManager(c);
+ currentManager.setDoubleBufferingEnabled(true);
+ }
+
+
+ /**
+ * Translate the page format coordinates onto the graphics object using Java's origin (top left).
+ *
+ * @param g2d the graphics object
+ * @param pageFormat the print page format
+ */
+ public static void translateToJavaOrigin(Graphics2D g2d, PageFormat pageFormat) {
+ g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
+ }
+
+ /**
+ * Add text as a new paragraph in a given font to the document.
+ *
+ * @param document the document
+ * @param font the font
+ * @param title the title
+ */
+ public static void addText(Document document, com.itextpdf.text.Font font, String title) {
+ Chunk sectionHeader = new Chunk(title);
+ sectionHeader.setFont(font);
+ try {
+ Paragraph p = new Paragraph();
+ p.add(sectionHeader);
+ document.add(p);
+ } catch (DocumentException e) {
+ log.error("Could not add paragraph.", e);
+ }
+ }
}
*/
package net.sf.openrocket.gui.print;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.TreeSet;
+import java.util.*;
/**
* Instances of this class are meant to keep track of what the user has selected to be printed.
*/
private OpenRocketPrintable printable;
+ /**
+ * Sort of a reverse map that tracks each type of printable item and the stages for which that item is to be printed.
+ */
private final Map<OpenRocketPrintable, Set<Integer>> previous = new TreeMap<OpenRocketPrintable, Set<Integer>>();
-
+ /**
+ * Constructor.
+ */
public PrintableContext () {
}
printable = thePrintable;
}
- public void add (final Integer theStageNumber, final OpenRocketPrintable thePrintable)
- throws IllegalArgumentException {
+ /**
+ * Add a type of printable to a stage (number).
+ *
+ * @param theStageNumber the stage number
+ * @param thePrintable the printable to associate with the stage
+ */
+ public void add (final Integer theStageNumber, final OpenRocketPrintable thePrintable) {
Set<Integer> stages = previous.get(thePrintable);
if (stages == null) {
stages = new TreeSet<Integer>();
}
}
-
+ /** PrintableContext iterator. */
public Iterator<PrintableContext> iterator () {
return new Iterator<PrintableContext>() {
*/
package net.sf.openrocket.gui.print;
-import javax.swing.UIManager;
import java.awt.Color;
+import javax.swing.UIManager;
+
/**
* This class is responsible for managing various properties of print templates (fin, nose cone, transitions, etc.).
+ *
+ * TODO: HIGH: Remove this entire class, and instead pass the PrintSettings object to the print methods.
*/
public class TemplateProperties {
-
- /**
- * The property that defines the fill color.
- */
- public static final String TEMPLATE_FILL_COLOR_PROPERTY = "template.fill.color";
-
- /**
- * The property that defines the line color.
- */
- public static final String TEMPLATE_LINE_COLOR_PROPERTY = "template.line.color";
-
- /**
- * Get the current fill color.
- *
- * @return a color to be used as the fill in template shapes
- */
- public static Color getFillColor () {
- Color fillColor = UIManager.getColor(TemplateProperties.TEMPLATE_FILL_COLOR_PROPERTY);
- if (fillColor == null) {
- fillColor = Color.lightGray;
- }
- return fillColor;
- }
-
- /**
- * Get the current line color.
- *
- * @return a color to be used as the line in template shapes
- */
- public static Color getLineColor () {
- Color lineColor = UIManager.getColor(TemplateProperties.TEMPLATE_LINE_COLOR_PROPERTY);
- if (lineColor == null) {
- lineColor = Color.darkGray;
- }
- return lineColor;
- }
+
+ /**
+ * The property that defines the fill color.
+ */
+ public static final String TEMPLATE_FILL_COLOR_PROPERTY = "template.fill.color";
+
+ /**
+ * The property that defines the line color.
+ */
+ public static final String TEMPLATE_LINE_COLOR_PROPERTY = "template.line.color";
+
+ /**
+ * Get the current fill color.
+ *
+ * @return a color to be used as the fill in template shapes
+ */
+ public static Color getFillColor() {
+ Color fillColor = UIManager.getColor(TemplateProperties.TEMPLATE_FILL_COLOR_PROPERTY);
+ if (fillColor == null) {
+ fillColor = Color.lightGray;
+ }
+ return fillColor;
+ }
+
+
+ /**
+ * Set the template fill color.
+ */
+ public static void setFillColor(Color c) {
+ UIManager.put(TemplateProperties.TEMPLATE_FILL_COLOR_PROPERTY, c);
+ }
+
+
+ /**
+ * Get the current line color.
+ *
+ * @return a color to be used as the line in template shapes
+ */
+ public static Color getLineColor() {
+ Color lineColor = UIManager.getColor(TemplateProperties.TEMPLATE_LINE_COLOR_PROPERTY);
+ if (lineColor == null) {
+ lineColor = Color.darkGray;
+ }
+ return lineColor;
+ }
+
+ /**
+ * Set the template line color.
+ */
+ public static void setLineColor(Color c) {
+ UIManager.put(TemplateProperties.TEMPLATE_LINE_COLOR_PROPERTY, c);
+ }
+
+ /**
+ * Set the template colors from the print settings.
+ */
+ public static void setColors(PrintSettings settings) {
+ setFillColor(settings.getTemplateFillColor());
+ setLineColor(settings.getTemplateBorderColor());
+ }
}
+++ /dev/null
-/*
- * BaseVisitorStrategy.java
- */
-package net.sf.openrocket.gui.print.visitor;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import net.sf.openrocket.rocketcomponent.BodyComponent;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-import net.sf.openrocket.rocketcomponent.ComponentVisitorStrategy;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
-
-import com.itextpdf.text.Document;
-import com.itextpdf.text.pdf.PdfWriter;
-
-/**
- * This abstract class contains boilerplate functionality to support visiting the components of a rocket. It is a
- * visitor strategy, not a visitor per se.
- */
-public abstract class BaseVisitorStrategy implements ComponentVisitorStrategy {
-
- /**
- * The owning visitor.
- */
- protected ComponentVisitor parent;
-
- /**
- * The iText document.
- */
- protected Document document;
-
- /**
- * The direct iText writer.
- */
- protected PdfWriter writer;
-
- /**
- * The stages selected.
- */
- protected Set<Integer> stages;
-
- /**
- * State variable to track the level of hierarchy.
- */
- protected int level = 0;
-
- /**
- * Default no-arg constructor.
- */
- public BaseVisitorStrategy() {
- }
-
- /**
- * Constructor.
- *
- * @param doc the iText document
- */
- public BaseVisitorStrategy(Document doc) {
- this(doc, null);
- }
-
- /**
- * Constructor.
- *
- * @param doc the iText document
- * @param theWriter an iText byte writer
- */
- public BaseVisitorStrategy(Document doc, PdfWriter theWriter) {
- this(doc, theWriter, new HashSet<Integer>());
- }
-
- /**
- * Constructor.
- *
- * @param doc the iText document
- * @param theWriter an iText byte writer
- * @param theStages a set of stage numbers
- */
- public BaseVisitorStrategy(Document doc, PdfWriter theWriter, Set<Integer> theStages) {
- document = doc;
- writer = theWriter;
- stages = theStages;
- }
-
- /**
- * Determine if the visitor strategy's set of stage numbers (to print) contains the specified stage.
- *
- * @param stageNumber a stage number
- *
- * @return true if the visitor strategy contains the stage number provided
- */
- public boolean shouldVisitStage(int stageNumber) {
- if (stages == null || stages.isEmpty()) {
- return false;
- }
-
- for (final Integer stage : stages) {
- if (stage == stageNumber) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Recurse through the given rocket component.
- *
- * @param root the root component; all children will be visited recursively
- */
- protected void goDeep(final RocketComponent root) {
- List<RocketComponent> rc = root.getChildren();
- goDeep(rc);
- }
-
-
- /**
- * Recurse through the given rocket component.
- *
- * @param theRc an array of rocket components; all children will be visited recursively
- */
- protected void goDeep(final List<RocketComponent> theRc) {
- level++;
- for (RocketComponent rocketComponent : theRc) {
- rocketComponent.accept(parent);
- }
- level--;
- }
-
- /**
- * Get the dimensions of the paper page.
- *
- * @return an internal Dimension
- */
- protected Dimension getPageSize() {
- return new Dimension(document.getPageSize().getWidth(),
- document.getPageSize().getHeight());
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Rocket visitable) {
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RocketComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Stage visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final ExternalComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RingComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final InnerTube visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final LaunchLug visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Transition visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RadiusRingComponent visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final MassObject visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final NoseCone visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyTube visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final TrapezoidFinSet visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final EllipticalFinSet visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final FreeformFinSet visitable) {
- if (shouldVisitStage(visitable.getStageNumber())) {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setParent(final ComponentVisitor theParent) {
- parent = theParent;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void close() {
- }
-}
-
-class Dimension {
- public float width;
- public float height;
-
- public Dimension(float w, float h) {
- width = w;
- height = h;
- }
-
- public float getWidth() {
- return width;
- }
-
- public float getHeight() {
- return height;
- }
-}
\ No newline at end of file
import com.itextpdf.text.pdf.PdfWriter;
import net.sf.openrocket.gui.print.ITextHelper;
import net.sf.openrocket.gui.print.PrintableFinSet;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.startup.Application;
-import java.awt.Graphics2D;
+import java.awt.*;
import java.awt.image.BufferedImage;
+import java.util.List;
import java.util.Set;
/**
- * A visitor strategy for drawing fin templates.
+ * A strategy for drawing fin templates.
*/
-public class FinSetVisitorStrategy extends BaseVisitorStrategy {
+public class FinSetVisitorStrategy {
+
+ /**
+ * The logger.
+ */
+ private static final LogHelper log = Application.getLogger();
+
+ /**
+ * The iText document.
+ */
+ protected Document document;
+
+ /**
+ * The direct iText writer.
+ */
+ protected PdfWriter writer;
+
+ /**
+ * The stages selected.
+ */
+ protected Set<Integer> stages;
/**
* Constructor.
* @param theStagesToVisit The stages to be visited by this strategy
*/
public FinSetVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
- super(doc, theWriter, theStagesToVisit);
+ document = doc;
+ writer = theWriter;
+ stages = theStagesToVisit;
}
/**
- * {@inheritDoc}
+ * Recurse through the given rocket component.
+ *
+ * @param root the root component; all children will be visited recursively
*/
- @Override
- public void visit (final TrapezoidFinSet visitable) {
- doVisit(visitable);
+ public void writeToDocument (final RocketComponent root) {
+ List<RocketComponent> rc = root.getChildren();
+ goDeep(rc);
}
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit (final EllipticalFinSet visitable) {
- doVisit(visitable);
- }
/**
- * {@inheritDoc}
+ * Recurse through the given rocket component.
+ *
+ * @param theRc an array of rocket components; all children will be visited recursively
*/
- @Override
- public void visit (final FreeformFinSet visitable) {
- doVisit(visitable);
+ protected void goDeep (final List<RocketComponent> theRc) {
+ for (RocketComponent rocketComponent : theRc) {
+ if (rocketComponent instanceof FinSet) {
+ doVisit((FinSet)rocketComponent);
+ }
+ else if (rocketComponent.getChildCount() > 0) {
+ goDeep(rocketComponent.getChildren());
+ }
+ }
}
/**
}
}
catch (DocumentException e) {
- e.printStackTrace();
+ log.error("Could not render fin.", e);
}
}
}
+ /**
+ * Determine if the visitor strategy's set of stage numbers (to print) contains the specified stage.
+ *
+ * @param stageNumber a stage number
+ *
+ * @return true if the visitor strategy contains the stage number provided
+ */
+ public boolean shouldVisitStage (int stageNumber) {
+ if (stages == null || stages.isEmpty()) {
+ return false;
+ }
+
+ for (final Integer stage : stages) {
+ if (stage == stageNumber) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Determine if the image will fit on the given page.
*
g2.dispose();
document.newPage();
}
-}
+ /**
+ * Get the dimensions of the paper page.
+ *
+ * @return an internal Dimension
+ */
+ protected Dimension getPageSize () {
+ return new Dimension(document.getPageSize().getWidth(),
+ document.getPageSize().getHeight());
+ }
+
+ /**
+ * Convenience class to model a dimension.
+ */
+ class Dimension {
+ /** Width, in points. */
+ public float width;
+ /** Height, in points. */
+ public float height;
+
+ /**
+ * Constructor.
+ * @param w width
+ * @param h height
+ */
+ public Dimension (float w, float h) {
+ width = w;
+ height = h;
+ }
+
+ /**
+ * Get the width.
+ *
+ * @return the width
+ */
+ public float getWidth () {
+ return width;
+ }
+
+ /**
+ * Get the height.
+ *
+ * @return the height
+ */
+ public float getHeight () {
+ return height;
+ }
+ }
+}
+++ /dev/null
-/*
- * MotorMountVisitor.java
- */
-package net.sf.openrocket.gui.print.visitor;
-
-import com.itextpdf.text.Document;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.MotorMount;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A visitor strategy for finding data about motor configurations. This visitor accumulates information about the
- * motors currently 'installed' into each motor mount in the rocket. When the visitor is complete, invoke {@link
- * #getMotors()} to obtain the list of Motor instances that correspond to the motor configuration.
- */
-public class MotorMountVisitorStrategy extends BaseVisitorStrategy {
-
- /**
- * The motor configuration identifier.
- */
- private String mid;
-
- /** The accumulating list of motors. */
- private List<Motor> motors = new ArrayList<Motor>();
-
- /**
- * Constructor.
- *
- * @param doc the iText document
- * @param motorConfigID the motor configuration ID
- */
- public MotorMountVisitorStrategy (Document doc,
- String motorConfigID) {
- super(doc);
- mid = motorConfigID;
- }
-
- /**
- * Override the method that determines if the visiting should be going deep.
- *
- * @param stageNumber a stage number
- *
- * @return true, always
- */
- public boolean shouldVisitStage (int stageNumber) {
- return true;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit (final BodyTube visitable) {
- if (visitable.isMotorMount()) {
- doVisit(visitable);
- }
- else {
- goDeep(visitable);
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit (final InnerTube visitable) {
- if (visitable.isMotorMount()) {
- doVisit(visitable);
- }
- }
-
- /**
- * The core behavior of this visitor.
- *
- * @param visitable the object to extract info about; a graphical image of the fin shape is drawn to the document
- */
- private void doVisit (final MotorMount visitable) {
- final Motor motor = visitable.getMotor(mid);
- if (motor != null) {
- motors.add(motor);
- }
- }
-
- /**
- * Answer with the list of motors that have been accumulated from visiting all of the motor mount components in the
- * rocket component hierarchy.
- *
- * @return a list of motors
- */
- public List<Motor> getMotors () {
- return motors;
- }
-
-}
-
-
*/
package net.sf.openrocket.gui.print.visitor;
-import java.io.IOException;
-import java.text.NumberFormat;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-import javax.swing.ImageIcon;
-
+import com.itextpdf.text.*;
+import com.itextpdf.text.pdf.PdfPCell;
+import com.itextpdf.text.pdf.PdfPTable;
+import com.itextpdf.text.pdf.PdfWriter;
import net.sf.openrocket.gui.main.ComponentIcons;
import net.sf.openrocket.gui.print.ITextHelper;
import net.sf.openrocket.gui.print.PrintUtilities;
import net.sf.openrocket.gui.print.PrintableFinSet;
+import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.material.Material;
-import net.sf.openrocket.rocketcomponent.BodyComponent;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.Coaxial;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-import net.sf.openrocket.rocketcomponent.FinSet;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
+import net.sf.openrocket.rocketcomponent.*;
+import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
-import com.itextpdf.text.BadElementException;
-import com.itextpdf.text.Chunk;
-import com.itextpdf.text.Document;
-import com.itextpdf.text.DocumentException;
-import com.itextpdf.text.Element;
-import com.itextpdf.text.Font;
-import com.itextpdf.text.Image;
-import com.itextpdf.text.Phrase;
-import com.itextpdf.text.Rectangle;
-import com.itextpdf.text.pdf.PdfPCell;
-import com.itextpdf.text.pdf.PdfPTable;
-import com.itextpdf.text.pdf.PdfWriter;
+import javax.swing.*;
+import java.text.NumberFormat;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
/**
* A visitor strategy for creating documentation about parts details.
*/
-public class PartsDetailVisitorStrategy extends BaseVisitorStrategy {
-
- /**
- * The number of columns in the table.
- */
- private static final int TABLE_COLUMNS = 7;
-
- /**
- * The parts detail is represented as an iText table.
- */
- PdfPTable grid;
-
- /**
- * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
- *
- * @param doc The iText document
- * @param theWriter The direct iText writer
- * @param theStagesToVisit The stages to be visited by this strategy
- */
- public PartsDetailVisitorStrategy(Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
- super(doc, theWriter, theStagesToVisit);
- PrintUtilities.addText(doc, PrintUtilities.BIG_BOLD, "Parts Detail");
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Stage visitable) {
- try {
- if (grid != null) {
- document.add(grid);
- }
- document.add(ITextHelper.createPhrase(visitable.getName()));
- grid = new PdfPTable(TABLE_COLUMNS);
- grid.setWidthPercentage(100);
- grid.setHorizontalAlignment(Element.ALIGN_LEFT);
- } catch (DocumentException e) {
- }
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final ExternalComponent visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
-
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(ITextHelper.createCell());
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyComponent visitable) {
- grid.addCell(visitable.getName());
- grid.completeRow();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RingComponent visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final InnerTube visitable) {
- grid.addCell(iconToImage(visitable));
- final PdfPCell pCell = createNameCell(visitable.getName(), true);
- grid.addCell(pCell);
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final LaunchLug visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
-
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Transition visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
-
- Chunk fore = new Chunk("Fore Dia: " + appendLength(visitable.getForeRadius() * 2));
- fore.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- Chunk aft = new Chunk("Aft Dia: " + appendLength(visitable.getAftRadius() * 2));
- aft.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- final PdfPCell cell = ITextHelper.createCell();
- cell.addElement(fore);
- cell.addElement(aft);
- grid.addCell(cell);
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RadiusRingComponent visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final MassObject visitable) {
- PdfPCell cell = ITextHelper.createCell();
- cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
- cell.setPaddingBottom(12f);
-
- grid.addCell(iconToImage(visitable));
- final PdfPCell nameCell = createNameCell(visitable.getName(), true);
- nameCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
- nameCell.setPaddingBottom(12f);
- grid.addCell(nameCell);
- grid.addCell(cell);
- grid.addCell(cell);
- grid.addCell(cell);
- grid.addCell(createMassCell(visitable.getMass()));
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final NoseCone visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(ITextHelper.createCell(visitable.getType().getName(), PdfPCell.BOTTOM));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyTube visitable) {
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName(), true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(createOuterDiaCell(visitable));
- grid.addCell(createLengthCell(visitable.getLength()));
- grid.addCell(createMassCell(visitable.getMass()));
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final TrapezoidFinSet visitable) {
- visitFins(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final EllipticalFinSet visitable) {
- visitFins(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final FreeformFinSet visitable) {
- visitFins(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void close() {
- try {
- if (grid != null) {
- document.add(grid);
- }
- } catch (DocumentException e) {
- e.printStackTrace();
- }
- }
-
- private PdfPCell createOuterDiaCell(final Coaxial visitable) {
- PdfPCell result = new PdfPCell();
- Phrase p = new Phrase();
- p.setLeading(12f);
- result.setVerticalAlignment(Element.ALIGN_TOP);
- result.setBorder(Rectangle.BOTTOM);
- Chunk c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append("Dia");
- p.add(c);
-
- c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
- c.append("out");
- p.add(c);
-
- c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append(" " + appendLength(visitable.getOuterRadius() * 2));
- p.add(c);
- createInnerDiaCell(visitable, result);
- result.addElement(p);
- return result;
- }
-
- private void createInnerDiaCell(final Coaxial visitable, PdfPCell cell) {
- Phrase p = new Phrase();
- p.setLeading(14f);
- Chunk c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append("Dia");
- p.add(c);
-
- c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
- c.append("in ");
- p.add(c);
-
- c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append(" " + appendLength(visitable.getInnerRadius() * 2));
- p.add(c);
- cell.addElement(p);
- }
-
- private void visitFins(FinSet visitable) {
-
- Image img = null;
- java.awt.Image awtImage = new PrintableFinSet(visitable).createImage();
-
- Collection<Coordinate> x = visitable.getComponentBounds();
-
- try {
- img = Image.getInstance(writer, awtImage, 0.25f);
- } catch (BadElementException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- grid.addCell(iconToImage(visitable));
- grid.addCell(createNameCell(visitable.getName() + " (" + visitable.getFinCount() + ")", true));
- grid.addCell(createMaterialCell(visitable.getMaterial()));
- grid.addCell(ITextHelper.createCell("Thick: " + appendLength(visitable.getThickness()), PdfPCell.BOTTOM));
- final PdfPCell pCell = new PdfPCell();
- pCell.setBorder(Rectangle.BOTTOM);
- pCell.addElement(img);
-
- grid.addCell(ITextHelper.createCell());
- grid.addCell(createMassCell(visitable.getMass()));
-
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- protected PdfPCell createLengthCell(double length) {
- return ITextHelper.createCell("Len: " + appendLength(length), PdfPCell.BOTTOM);
- }
-
- protected PdfPCell createMassCell(double mass) {
- return ITextHelper.createCell("Mass: " + appendMass(mass), PdfPCell.BOTTOM);
- }
-
- protected PdfPCell createNameCell(String v, boolean withIndent) {
- PdfPCell result = new PdfPCell();
- result.setBorder(Rectangle.BOTTOM);
- Chunk c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- if (withIndent) {
- for (int x = 0; x < (level - 2) * 10; x++) {
- c.append(" ");
- }
- }
- c.append(v);
- result.setColspan(2);
- result.addElement(c);
- return result;
- }
-
- protected PdfPCell createMaterialCell(Material material) {
- PdfPCell cell = ITextHelper.createCell();
- cell.setLeading(13f, 0);
-
- Chunk c = new Chunk();
- c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
- c.append(appendMaterial(material));
- cell.addElement(c);
- Chunk density = new Chunk();
- density.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
- density.append(appendMaterialDensity(material));
- cell.addElement(density);
- return cell;
- }
-
- protected PdfPCell iconToImage(final RocketComponent visitable) {
- final ImageIcon icon = (ImageIcon) ComponentIcons.getLargeIcon(visitable.getClass());
- try {
- Image im = Image.getInstance(icon.getImage(), null);
- im.scaleToFit(icon.getIconWidth() * 0.6f, icon.getIconHeight() * 0.6f);
- PdfPCell cell = new PdfPCell(im);
- cell.setFixedHeight(icon.getIconHeight() * 0.6f);
- cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
- cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
- cell.setBorder(PdfPCell.NO_BORDER);
- return cell;
- } catch (BadElementException e) {
- } catch (IOException e) {
- }
- return null;
- }
-
- protected String appendLength(double length) {
- final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
- return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(length)) + defaultUnit.toString();
- }
-
- protected String appendMass(double mass) {
- final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
- return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(mass)) + defaultUnit.toString();
- }
-
- protected String appendMaterial(Material material) {
- return material.getName();
- }
-
- protected String appendMaterialDensity(Material material) {
- return " (" + material.getType().getUnitGroup().getDefaultUnit().toStringUnit(material.getDensity()) + ")";
- }
-
+public class PartsDetailVisitorStrategy {
+
+ /**
+ * The logger.
+ */
+ private static final LogHelper log = Application.getLogger();
+
+ /**
+ * The number of columns in the table.
+ */
+ private static final int TABLE_COLUMNS = 7;
+
+ /**
+ * The parts detail is represented as an iText table.
+ */
+ PdfPTable grid;
+
+ /**
+ * The iText document.
+ */
+ protected Document document;
+
+ /**
+ * The direct iText writer.
+ */
+ protected PdfWriter writer;
+
+ /**
+ * The stages selected.
+ */
+ protected Set<Integer> stages;
+
+ /**
+ * State variable to track the level of hierarchy.
+ */
+ protected int level = 0;
+
+ private static final String LINES = "Lines: ";
+ private static final String MASS = "Mass: ";
+ private static final String LEN = "Len: ";
+ private static final String THICK = "Thick: ";
+ private static final String INNER = "in ";
+ private static final String DIAMETER = "Dia";
+ private static final String OUTER = "out";
+ private static final String WIDTH = "Width";
+ private static final String LENGTH = "Length";
+ private static final String SHROUD_LINES = "Shroud Lines";
+ private static final String AFT_DIAMETER = "Aft Dia: ";
+ private static final String FORE_DIAMETER = "Fore Dia: ";
+ private static final String PARTS_DETAIL = "Parts Detail";
+
+ /**
+ * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
+ *
+ * @param doc The iText document
+ * @param theWriter The direct iText writer
+ * @param theStagesToVisit The stages to be visited by this strategy
+ */
+ public PartsDetailVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
+ document = doc;
+ writer = theWriter;
+ stages = theStagesToVisit;
+ PrintUtilities.addText(doc, PrintUtilities.BIG_BOLD, PARTS_DETAIL);
+ }
+
+ /**
+ * Print the parts detail.
+ *
+ * @param root the root component
+ */
+ public void writeToDocument (final RocketComponent root) {
+ goDeep(root.getChildren());
+ }
+
+ /**
+ * Recurse through the given rocket component.
+ *
+ * @param theRc an array of rocket components; all children will be visited recursively
+ */
+ protected void goDeep (final List<RocketComponent> theRc) {
+ level++;
+ for (RocketComponent rocketComponent : theRc) {
+ handle(rocketComponent);
+ }
+ level--;
+ }
+
+ /**
+ * Add a line to the detail report based upon the type of the component.
+ *
+ * @param component the component to print the detail for
+ */
+ private void handle (RocketComponent component) {
+ //This ugly if-then-else construct is not object oriented. Originally it was an elegant, and very OO savy, design
+ //using the Visitor pattern. Unfortunately, it was misunderstood and was removed.
+ if (component instanceof Stage) {
+ try {
+ if (grid != null) {
+ document.add(grid);
+ }
+ document.add(ITextHelper.createPhrase(component.getName()));
+ grid = new PdfPTable(TABLE_COLUMNS);
+ grid.setWidthPercentage(100);
+ grid.setHorizontalAlignment(Element.ALIGN_LEFT);
+ }
+ catch (DocumentException e) {
+ }
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof LaunchLug) {
+ LaunchLug ll = (LaunchLug) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+
+ grid.addCell(createMaterialCell(ll.getMaterial()));
+ grid.addCell(createOuterInnerDiaCell(ll));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ else if (component instanceof NoseCone) {
+ NoseCone nc = (NoseCone) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(nc.getMaterial()));
+ grid.addCell(ITextHelper.createCell(nc.getType().getName(), PdfPCell.BOTTOM));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof Transition) {
+ Transition tran = (Transition) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(tran.getMaterial()));
+
+ Chunk fore = new Chunk(FORE_DIAMETER + toLength(tran.getForeRadius() * 2));
+ fore.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ Chunk aft = new Chunk(AFT_DIAMETER + toLength(tran.getAftRadius() * 2));
+ aft.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ final PdfPCell cell = ITextHelper.createCell();
+ cell.addElement(fore);
+ cell.addElement(aft);
+ grid.addCell(cell);
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof BodyTube) {
+ BodyTube bt = (BodyTube) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(bt.getMaterial()));
+ grid.addCell(createOuterInnerDiaCell(bt));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof FinSet) {
+ handleFins((FinSet) component);
+ }
+ else if (component instanceof BodyComponent) {
+ grid.addCell(component.getName());
+ grid.completeRow();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof ExternalComponent) {
+ ExternalComponent ext = (ExternalComponent) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+
+ grid.addCell(createMaterialCell(ext.getMaterial()));
+ grid.addCell(ITextHelper.createCell());
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof InnerTube) {
+ InnerTube it = (InnerTube) component;
+ grid.addCell(iconToImage(component));
+ final PdfPCell pCell = createNameCell(component.getName(), true);
+ grid.addCell(pCell);
+ grid.addCell(createMaterialCell(it.getMaterial()));
+ grid.addCell(createOuterInnerDiaCell(it));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof RadiusRingComponent) {
+ RadiusRingComponent rrc = (RadiusRingComponent) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(rrc.getMaterial()));
+ if (component instanceof Bulkhead) {
+ grid.addCell(createDiaCell(rrc.getOuterRadius()*2));
+ }
+ else {
+ grid.addCell(createOuterInnerDiaCell(rrc));
+ }
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof RingComponent) {
+ RingComponent ring = (RingComponent) component;
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(ring.getMaterial()));
+ grid.addCell(createOuterInnerDiaCell(ring));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof ShockCord) {
+ ShockCord ring = (ShockCord) component;
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(ring.getMaterial()));
+ grid.addCell(cell);
+ grid.addCell(createLengthCell(ring.getCordLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ else if (component instanceof Parachute) {
+ Parachute chute = (Parachute) component;
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(chute.getMaterial()));
+ grid.addCell(createDiaCell(chute.getDiameter()));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+
+ grid.addCell(iconToImage(null));
+ grid.addCell(createNameCell(SHROUD_LINES, true));
+ grid.addCell(createMaterialCell(chute.getLineMaterial()));
+ grid.addCell(createLinesCell(chute.getLineCount()));
+ grid.addCell(createLengthCell(chute.getLineLength()));
+ grid.addCell(cell);
+ }
+ else if (component instanceof Streamer) {
+ Streamer ring = (Streamer) component;
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(ring.getMaterial()));
+ grid.addCell(createStrip(ring));
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ else if (component instanceof RecoveryDevice) {
+ RecoveryDevice device = (RecoveryDevice) component;
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+ grid.addCell(iconToImage(component));
+ grid.addCell(createNameCell(component.getName(), true));
+ grid.addCell(createMaterialCell(device.getMaterial()));
+ grid.addCell(cell);
+ grid.addCell(createLengthCell(component.getLength()));
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ else if (component instanceof MassObject) {
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setPaddingBottom(12f);
+
+ grid.addCell(iconToImage(component));
+ final PdfPCell nameCell = createNameCell(component.getName(), true);
+ nameCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ nameCell.setPaddingBottom(12f);
+ grid.addCell(nameCell);
+ grid.addCell(cell);
+ grid.addCell(createDiaCell(((MassObject) component).getRadius() * 2));
+ grid.addCell(cell);
+ grid.addCell(createMassCell(component.getMass()));
+ }
+ }
+
+ /**
+ * Close the strategy by adding the last grid to the document.
+ */
+ public void close () {
+ try {
+ if (grid != null) {
+ document.add(grid);
+ }
+ }
+ catch (DocumentException e) {
+ log.error("Could not write last cell to document.", e);
+ }
+ }
+
+ /**
+ * Create a cell to document an outer 'diameter'. This is used for components that have no inner diameter, such as
+ * a solid parachute or bulkhead.
+ *
+ * @param diameter the diameter in default length units
+ *
+ * @return a formatted cell containing the diameter
+ */
+ private PdfPCell createDiaCell (final double diameter) {
+ PdfPCell result = new PdfPCell();
+ Phrase p = new Phrase();
+ p.setLeading(12f);
+ result.setVerticalAlignment(Element.ALIGN_TOP);
+ result.setBorder(Rectangle.BOTTOM);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(DIAMETER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
+ c.append(OUTER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(diameter));
+ p.add(c);
+ result.addElement(p);
+ return result;
+ }
+
+ /**
+ * Create a PDF cell for a streamer.
+ *
+ * @param component a component that is a Coaxial
+ * @return the PDF cell that has the streamer documented
+ */
+ private PdfPCell createStrip (final Streamer component) {
+ PdfPCell result = new PdfPCell();
+ Phrase p = new Phrase();
+ p.setLeading(12f);
+ result.setVerticalAlignment(Element.ALIGN_TOP);
+ result.setBorder(Rectangle.BOTTOM);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(LENGTH);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(component.getStripLength()));
+ p.add(c);
+ result.addElement(p);
+
+ Phrase pw = new Phrase();
+ pw.setLeading(14f);
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(WIDTH);
+ pw.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(component.getStripWidth()));
+ pw.add(c);
+ result.addElement(pw);
+
+ return result;
+ }
+
+ /**
+ * Create a PDF cell that documents both an outer and an inner diameter of a component.
+ *
+ * @param component a component that is a Coaxial
+ * @return the PDF cell that has the outer and inner diameters documented
+ */
+ private PdfPCell createOuterInnerDiaCell (final Coaxial component) {
+ PdfPCell result = new PdfPCell();
+ Phrase p = new Phrase();
+ p.setLeading(12f);
+ result.setVerticalAlignment(Element.ALIGN_TOP);
+ result.setBorder(Rectangle.BOTTOM);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(DIAMETER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
+ c.append(OUTER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(component.getOuterRadius() * 2));
+ p.add(c);
+ createInnerDiaCell(component, result);
+ result.addElement(p);
+ return result;
+ }
+
+ /**
+ * Add inner diameter data to a cell.
+ *
+ * @param component a component that is a Coaxial
+ * @param cell the PDF cell to add the inner diameter data to
+ */
+ private void createInnerDiaCell (final Coaxial component, PdfPCell cell) {
+ Phrase p = new Phrase();
+ p.setLeading(14f);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(DIAMETER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
+ c.append(INNER);
+ p.add(c);
+
+ c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(" " + toLength(component.getInnerRadius() * 2));
+ p.add(c);
+ cell.addElement(p);
+ }
+
+ /**
+ * Add PDF cells for a fin set.
+ *
+ * @param theFinSet the fin set
+ */
+ private void handleFins (FinSet theFinSet) {
+
+ Image img = null;
+ java.awt.Image awtImage = new PrintableFinSet(theFinSet).createImage();
+
+ Collection<Coordinate> x = theFinSet.getComponentBounds();
+
+ try {
+ img = Image.getInstance(writer, awtImage, 0.25f);
+ }
+ catch (Exception e) {
+ log.error("Could not write image to document.", e);
+ }
+
+ grid.addCell(iconToImage(theFinSet));
+ grid.addCell(createNameCell(theFinSet.getName() + " (" + theFinSet.getFinCount() + ")", true));
+ grid.addCell(createMaterialCell(theFinSet.getMaterial()));
+ grid.addCell(ITextHelper.createCell(THICK + toLength(theFinSet.getThickness()), PdfPCell.BOTTOM));
+ final PdfPCell pCell = new PdfPCell();
+ pCell.setBorder(Rectangle.BOTTOM);
+ pCell.addElement(img);
+
+ grid.addCell(ITextHelper.createCell());
+ grid.addCell(createMassCell(theFinSet.getMass()));
+
+ List<RocketComponent> rc = theFinSet.getChildren();
+ goDeep(rc);
+ }
+
+ /**
+ * Create a length formatted cell.
+ *
+ * @param length the length, in default length units
+ *
+ * @return a PdfPCell that is formatted with the length
+ */
+ protected PdfPCell createLengthCell (double length) {
+ return ITextHelper.createCell(LEN + toLength(length), PdfPCell.BOTTOM);
+ }
+
+ /**
+ * Create a mass formatted cell.
+ *
+ * @param mass the mass, in default mass units
+ *
+ * @return a PdfPCell that is formatted with the mass
+ */
+ protected PdfPCell createMassCell (double mass) {
+ return ITextHelper.createCell(MASS + toMass(mass), PdfPCell.BOTTOM);
+ }
+
+ /**
+ * Create a (shroud) line count formatted cell.
+ *
+ * @param count the number of shroud lines
+ *
+ * @return a PdfPCell that is formatted with the line count
+ */
+ protected PdfPCell createLinesCell (int count) {
+ return ITextHelper.createCell(LINES + count, PdfPCell.BOTTOM);
+ }
+
+ /**
+ * Create a cell formatted for a name (or any string for that matter).
+ *
+ * @param v the string to format into a PDF cell
+ * @param withIndent if true, then an indention is made scaled to the level of the part in the parent hierarchy
+ *
+ * @return a PdfPCell that is formatted with the string <code>v</code>
+ */
+ protected PdfPCell createNameCell (String v, boolean withIndent) {
+ PdfPCell result = new PdfPCell();
+ result.setBorder(Rectangle.BOTTOM);
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ if (withIndent) {
+ for (int x = 0; x < (level - 2) * 10; x++) {
+ c.append(" ");
+ }
+ }
+ c.append(v);
+ result.setColspan(2);
+ result.addElement(c);
+ return result;
+ }
+
+ /**
+ * Create a cell that describes a material.
+ *
+ * @param material the material
+ *
+ * @return a PdfPCell that is formatted with a description of the material
+ */
+ protected PdfPCell createMaterialCell (Material material) {
+ PdfPCell cell = ITextHelper.createCell();
+ cell.setLeading(13f, 0);
+
+ Chunk c = new Chunk();
+ c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
+ c.append(toMaterialName(material));
+ cell.addElement(c);
+ Chunk density = new Chunk();
+ density.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
+ density.append(toMaterialDensity(material));
+ cell.addElement(density);
+ return cell;
+ }
+
+ /**
+ * Get the icon of the particular type of rocket component and conver it to an image in a PDF cell.
+ *
+ * @param visitable the rocket component to create a cell with it's image
+ *
+ * @return a PdfPCell that is just an image that can be put into a PDF
+ */
+ protected PdfPCell iconToImage (final RocketComponent visitable) {
+ if (visitable != null) {
+ final ImageIcon icon = (ImageIcon) ComponentIcons.getLargeIcon(visitable.getClass());
+ try {
+ if (icon != null) {
+ Image im = Image.getInstance(icon.getImage(), null);
+ if (im != null) {
+ im.scaleToFit(icon.getIconWidth() * 0.6f, icon.getIconHeight() * 0.6f);
+ PdfPCell cell = new PdfPCell(im);
+ cell.setFixedHeight(icon.getIconHeight() * 0.6f);
+ cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setBorder(PdfPCell.NO_BORDER);
+ return cell;
+ }
+ }
+ }
+ catch (Exception e) {
+ }
+ }
+ PdfPCell cell = new PdfPCell();
+ cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
+ cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
+ cell.setBorder(PdfPCell.NO_BORDER);
+ return cell;
+ }
+
+ /**
+ * Format the length as a displayable string.
+ *
+ * @param length the length (assumed to be in default length units)
+ *
+ * @return a string representation of the length with unit abbreviation
+ */
+ protected String toLength (double length) {
+ final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
+ return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(length)) + defaultUnit.toString();
+ }
+
+ /**
+ * Format the mass as a displayable string.
+ *
+ * @param mass the mass (assumed to be in default mass units)
+ *
+ * @return a string representation of the mass with mass abbreviation
+ */
+ protected String toMass (double mass) {
+ final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
+ return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(mass)) + defaultUnit.toString();
+ }
+
+ /**
+ * Get a displayable string of the material's name.
+ *
+ * @param material the material to output
+ *
+ * @return the material name
+ */
+ protected String toMaterialName (Material material) {
+ return material.getName();
+ }
+
+ /**
+ * Format the material density as a displayable string.
+ *
+ * @param material the material to output
+ *
+ * @return a string representation of the material density
+ */
+ protected String toMaterialDensity (Material material) {
+ return " (" + material.getType().getUnitGroup().getDefaultUnit().toStringUnit(material.getDensity()) + ")";
+ }
+
}
*/
package net.sf.openrocket.gui.print.visitor;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.pdf.PdfWriter;
+import net.sf.openrocket.rocketcomponent.*;
+
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.Coaxial;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
-
-import com.itextpdf.text.Document;
-import com.itextpdf.text.pdf.PdfWriter;
-
/**
* A visitor strategy for creating documentation about a parts list.
*/
-public class PartsListVisitorStrategy extends BaseVisitorStrategy {
-
- /**
- * Accumulator for parts data.
- */
- private Map<PartsAccumulator, PartsAccumulator> crap = new HashMap<PartsAccumulator, PartsAccumulator>();
-
- /**
- * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
- *
- * @param doc The iText document
- * @param theWriter The direct iText writer
- * @param theStagesToVisit The stages to be visited by this strategy
- */
- public PartsListVisitorStrategy(Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
- super(doc, theWriter, theStagesToVisit);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RingComponent visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final InnerTube visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final LaunchLug visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Transition visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RadiusRingComponent visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final NoseCone visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyTube visitable) {
- final PartsAccumulator key = new PartsAccumulator(visitable);
- PartsAccumulator pa = crap.get(key);
- if (pa == null) {
- pa = key;
- crap.put(pa, pa);
- }
- pa.increment();
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final TrapezoidFinSet visitable) {
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final EllipticalFinSet visitable) {
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final FreeformFinSet visitable) {
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setParent(final ComponentVisitor theParent) {
- parent = theParent;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void close() {
- for (PartsAccumulator partsAccumulator : crap.keySet()) {
- System.err.println(partsAccumulator.component.getComponentName() + " " + partsAccumulator.quantity);
- }
- }
-
+public class PartsListVisitorStrategy {
+
+ /**
+ * Accumulator for parts data.
+ */
+ private Map<PartsAccumulator, PartsAccumulator> crap = new HashMap<PartsAccumulator, PartsAccumulator>();
+
+ /**
+ * The iText document.
+ */
+ protected Document document;
+
+ /**
+ * The direct iText writer.
+ */
+ protected PdfWriter writer;
+
+ /**
+ * The stages selected.
+ */
+ protected Set<Integer> stages;
+
+
+ /**
+ * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
+ *
+ * @param doc The iText document
+ * @param theWriter The direct iText writer
+ * @param theStagesToVisit The stages to be visited by this strategy
+ */
+ public PartsListVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
+ document = doc;
+ writer = theWriter;
+ stages = theStagesToVisit;
+ }
+
+
+ /**
+ * Print the parts detail.
+ *
+ * @param root the root component
+ */
+ public void doVisit (final RocketComponent root) {
+ goDeep(root.getChildren());
+ }
+
+ /**
+ * Recurse through the given rocket component.
+ *
+ * @param theRc an array of rocket components; all children will be visited recursively
+ */
+ protected void goDeep (final List<RocketComponent> theRc) {
+ for (RocketComponent rocketComponent : theRc) {
+ doIt(rocketComponent);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ private void doIt (final RocketComponent component) {
+ if (component instanceof InnerTube) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof LaunchLug) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ }
+
+ else if (component instanceof NoseCone) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof Transition) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof RadiusRingComponent) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof RingComponent) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof BodyTube) {
+ final PartsAccumulator key = new PartsAccumulator(component);
+ PartsAccumulator pa = crap.get(key);
+ if (pa == null) {
+ pa = key;
+ crap.put(pa, pa);
+ }
+ pa.increment();
+ List<RocketComponent> rc = component.getChildren();
+ goDeep(rc);
+ }
+ else if (component instanceof TrapezoidFinSet) {
+ }
+ else if (component instanceof EllipticalFinSet) {
+ }
+ else if (component instanceof FreeformFinSet) {
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void close () {
+ for (PartsAccumulator partsAccumulator : crap.keySet()) {
+ System.err.println(partsAccumulator.component.getComponentName() + " " + partsAccumulator.quantity);
+ }
+ }
+
}
class PartsAccumulator {
-
- int quantity = 0;
-
- RocketComponent component;
-
- PartsAccumulator(RocketComponent theComponent) {
- component = theComponent;
- }
-
- void increment() {
- quantity++;
- }
-
- int quantity() {
- return quantity;
- }
-
- @Override
- public boolean equals(final Object o1) {
- if (this == o1) {
- return true;
- }
-
- RocketComponent that;
- if (o1 instanceof net.sf.openrocket.gui.print.visitor.PartsAccumulator) {
- that = ((net.sf.openrocket.gui.print.visitor.PartsAccumulator) o1).component;
- } else if (o1 instanceof RocketComponent) {
- that = (RocketComponent) o1;
- } else {
- return false;
- }
-
- if (this.component.getClass().equals(that.getClass())) {
- //If
- if (that.getLength() == this.component.getLength()) {
- if (that.getMass() == this.component.getMass()) {
- return true;
- }
- }
- if (this.component instanceof Coaxial &&
- that instanceof Coaxial) {
- Coaxial cThis = (Coaxial) this.component;
- Coaxial cThat = (Coaxial) that;
- if (cThis.getInnerRadius() == cThat.getInnerRadius() &&
- cThis.getOuterRadius() == cThat.getOuterRadius()) {
- return true;
- }
- }
- return false;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return component.getComponentName().hashCode();
- }
+
+ int quantity = 0;
+
+ RocketComponent component;
+
+ PartsAccumulator (RocketComponent theComponent) {
+ component = theComponent;
+ }
+
+ void increment () {
+ quantity++;
+ }
+
+ int quantity () {
+ return quantity;
+ }
+
+ @Override
+ public boolean equals (final Object o1) {
+ if (this == o1) {
+ return true;
+ }
+
+ RocketComponent that;
+ if (o1 instanceof net.sf.openrocket.gui.print.visitor.PartsAccumulator) {
+ that = ((net.sf.openrocket.gui.print.visitor.PartsAccumulator) o1).component;
+ }
+ else if (o1 instanceof RocketComponent) {
+ that = (RocketComponent) o1;
+ }
+ else {
+ return false;
+ }
+
+ if (this.component.getClass().equals(that.getClass())) {
+ //If
+ if (that.getLength() == this.component.getLength()) {
+ if (that.getMass() == this.component.getMass()) {
+ return true;
+ }
+ }
+ if (this.component instanceof Coaxial &&
+ that instanceof Coaxial) {
+ Coaxial cThis = (Coaxial) this.component;
+ Coaxial cThat = (Coaxial) that;
+ if (cThis.getInnerRadius() == cThat.getInnerRadius() &&
+ cThis.getOuterRadius() == cThat.getOuterRadius()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode () {
+ return component.getComponentName().hashCode();
+ }
}
\ No newline at end of file
+++ /dev/null
-/*
- * StageVisitor.java
- */
-package net.sf.openrocket.gui.print.visitor;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import net.sf.openrocket.rocketcomponent.BodyComponent;
-import net.sf.openrocket.rocketcomponent.BodyTube;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
-import net.sf.openrocket.rocketcomponent.ComponentVisitorStrategy;
-import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
-import net.sf.openrocket.rocketcomponent.ExternalComponent;
-import net.sf.openrocket.rocketcomponent.FreeformFinSet;
-import net.sf.openrocket.rocketcomponent.InnerTube;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-import net.sf.openrocket.rocketcomponent.MassObject;
-import net.sf.openrocket.rocketcomponent.NoseCone;
-import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
-import net.sf.openrocket.rocketcomponent.RingComponent;
-import net.sf.openrocket.rocketcomponent.Rocket;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.rocketcomponent.Stage;
-import net.sf.openrocket.rocketcomponent.Transition;
-import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
-
-/**
- * This visitor strategy accumulates Stage references in a Rocket hierarchy.
- */
-public class StageVisitorStrategy implements ComponentVisitorStrategy {
-
- /**
- * The collection of stages, accumulated during a visitation.
- */
- private List<Double> stageComponents = new ArrayList<Double>();
-
- private Double mass = 0d;
-
- /**
- * The owning visitor.
- */
- protected ComponentVisitor parent;
-
- /**
- * Constructor.
- */
- public StageVisitorStrategy() {
- }
-
- /**
- * Override the method that determines if the visiting should be going deep.
- *
- * @param stageNumber a stage number
- *
- * @return true, always
- */
- public boolean shouldVisitStage(int stageNumber) {
- return true;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setParent(final ComponentVisitor theParent) {
- parent = theParent;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Rocket visitable) {
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Stage visitable) {
-
- if (mass > 0d) {
- stageComponents.add(mass);
- }
- mass = 0d;
- List<RocketComponent> rc = visitable.getChildren();
- goDeep(rc);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RocketComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * Recurse through the given rocket component.
- *
- * @param root the root component; all children will be visited recursively
- */
- protected void goDeep(final RocketComponent root) {
- List<RocketComponent> rc = root.getChildren();
- goDeep(rc);
- }
-
-
- /**
- * Recurse through the given rocket component.
- *
- * @param theRc an array of rocket components; all children will be visited recursively
- */
- protected void goDeep(final List<RocketComponent> theRc) {
- for (RocketComponent rocketComponent : theRc) {
- rocketComponent.accept(parent);
- }
- }
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final ExternalComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RingComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final InnerTube visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final LaunchLug visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final Transition visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final RadiusRingComponent visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final MassObject visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final NoseCone visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final BodyTube visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final TrapezoidFinSet visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final EllipticalFinSet visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void visit(final FreeformFinSet visitable) {
- mass += visitable.getMass();
- goDeep(visitable);
- }
-
- /**
- * Get the list of stages, sort from Stage 1 .. Stage N.
- *
- * @return a sorted list of stages
- */
- public List<Double> getStages() {
- return stageComponents;
- }
-
- /**
- * Close by setting the last stage.
- */
- @Override
- public void close() {
- if (mass > 0d) {
- stageComponents.add(mass);
- }
- }
-
-}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import java.util.MissingResourceException;
+
+import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.util.BugException;
+
+/**
+ * A translator that prepends a pre-defined class name in front of a translation key
+ * and retrieves the translator for that key, and only if that is missing reverts to
+ * the base key name. The base class name can either be provided to the constructor
+ * or retrieved from the stack.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ClassBasedTranslator implements Translator {
+
+
+ private final Translator translator;
+ private final String className;
+
+ /**
+ * Construct a translator using a specified class name.
+ *
+ * @param translator the translator from which to obtain the translations.
+ * @param className the base class name to prepend.
+ */
+ public ClassBasedTranslator(Translator translator, String className) {
+ this.translator = translator;
+ this.className = className;
+ }
+
+ /**
+ * Construct a translator by obtaining the base class name from the stack.
+ *
+ * @param translator the translator from which to obtain the translations.
+ * @param levels the number of levels to move upwards in the stack from the point where this method is called.
+ */
+ public ClassBasedTranslator(Translator translator, int levels) {
+ this(translator, getStackClass(levels));
+ }
+
+
+
+ @Override
+ public String get(String key) {
+ String classKey = className + "." + key;
+
+ try {
+ return translator.get(classKey);
+ } catch (MissingResourceException e) {
+ // Ignore
+ }
+
+ try {
+ return translator.get(key);
+ } catch (MissingResourceException e) {
+ MissingResourceException mre = new MissingResourceException(
+ "Neither key '" + classKey + "' nor '" + key + "' could be found", e.getClassName(), key);
+ mre.initCause(e);
+ throw mre;
+ }
+ }
+
+
+
+ private static String getStackClass(int levels) {
+ TraceException trace = new TraceException();
+ StackTraceElement stack[] = trace.getStackTrace();
+ final int index = levels + 2;
+ if (stack.length <= index) {
+ throw new BugException("Stack trace is too short, length=" + stack.length + ", expected=" + index, trace);
+ }
+
+ StackTraceElement element = stack[index];
+ String cn = element.getClassName();
+ int pos = cn.lastIndexOf('.');
+ if (pos >= 0) {
+ cn = cn.substring(pos + 1);
+ }
+ return cn;
+ }
+
+
+
+
+ // For unit testing purposes
+ String getClassName() {
+ return className;
+ }
+
+}
/**
* A translator implementation that returns the logical key in brackets instead
- * of an actual translation.
+ * of an actual translation. The class optionally verifies that the translation
+ * is actually obtainable from some other translator.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class DebugTranslator implements Translator {
+ private final Translator translator;
+
+
+ /**
+ * Sole constructor.
+ *
+ * @param translator the translator to verify the translation exists, or <code>null</code> not to verify.
+ */
+ public DebugTranslator(Translator translator) {
+ this.translator = translator;
+ }
+
+
+
@Override
public String get(String key) {
+ if (translator != null) {
+ translator.get(key);
+ }
return "[" + key + "]";
}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.Set;
+
+import net.sf.openrocket.gui.main.ExceptionHandler;
+
+/**
+ * A translator that suppresses MissingResourceExceptions and handles them gracefully.
+ * For every missing key this class calls the exception handler exactly once, and
+ * returns the key itself as the translation.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ExceptionSuppressingTranslator implements Translator {
+
+ private static final Set<String> errors = new HashSet<String>();
+ // For unit testing:
+ static int failures = 0;
+
+ private final Translator translator;
+
+
+
+ /**
+ * Sole constructor.
+ *
+ * @param translator the translator to use
+ */
+ public ExceptionSuppressingTranslator(Translator translator) {
+ this.translator = translator;
+ }
+
+
+
+ @Override
+ public String get(String key) {
+ try {
+ return translator.get(key);
+ } catch (MissingResourceException e) {
+ handleError(key, e);
+ }
+
+ return key;
+ }
+
+
+
+ private static synchronized void handleError(String key, MissingResourceException e) {
+ if (errors.add(key)) {
+ failures++;
+ ExceptionHandler.handleErrorCondition("Can not find translation for '" + key + "' locale=" + Locale.getDefault(), e);
+ }
+ }
+
+}
package net.sf.openrocket.l10n;
import java.util.Locale;
-import java.util.MissingResourceException;
import java.util.ResourceBundle;
-import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.startup.Application;
-
/**
* A translator that obtains translated strings from a resource bundle.
- * <p>
- * If a message is not found in any resource bundle, an error is logged and the key itself
- * is returned.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class ResourceBundleTranslator implements Translator {
- private static final LogHelper log = Application.getLogger();
private final ResourceBundle bundle;
- private final String baseName;
- private final Locale locale;
/**
* Create a ResourceBundleTranslator using the default Locale.
*/
public ResourceBundleTranslator(String baseName, Locale locale) {
this.bundle = ResourceBundle.getBundle(baseName, locale);
- this.baseName = baseName;
- this.locale = locale;
}
*/
@Override
public synchronized String get(String key) {
- try {
- return bundle.getString(key);
- } catch (MissingResourceException e) {
- log.error("String not found for key '" + key + "' in bundle '" + baseName + "' with locale " + locale, e);
- }
- return key;
+ return bundle.getString(key);
}
}
package net.sf.openrocket.l10n;
+import java.util.MissingResourceException;
+
/**
* An interface for obtaining translations from logical keys.
* <p>
*
* @param key the logical string key.
* @return the translated string.
+ * @throws MissingResourceException if the translation corresponding to the key is not found.
* @throws NullPointerException if key is null.
*/
public String get(String key);
/**
* Class to represent a body object. The object can be described as a function of
- * the cylindrical coordinates x and angle theta as r = f(x,theta). The component
+ * the cylindrical coordinates x and angle theta as r = f(x,theta). The component
* need not be symmetrical in any way (e.g. square tube, slanted cone etc).
- *
+ *
* It defines the methods getRadius(x,theta) and getInnerRadius(x,theta), as well
* as get/setLength().
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class BodyComponent extends ExternalComponent {
-
+
/**
* Default constructor. Sets the relative position to POSITION_RELATIVE_AFTER,
* i.e. body components come after one another.
public BodyComponent() {
super(RocketComponent.Position.AFTER);
}
-
-
+
+
/**
* Get the outer radius of the component at cylindrical coordinate (x,theta).
- *
+ *
* Note that the return value may be negative for a slanted object.
- *
+ *
* @param x Distance in x direction
* @param theta Angle about the x-axis
* @return Distance to the outer edge of the object
*/
public abstract double getRadius(double x, double theta);
-
-
+
+
/**
* Get the inner radius of the component at cylindrical coordinate (x,theta).
- *
+ *
* Note that the return value may be negative for a slanted object.
- *
+ *
* @param x Distance in x direction
* @param theta Angle about the x-axis
* @return Distance to the inner edge of the object
*/
public abstract double getInnerRadius(double x, double theta);
-
-
+
+
/**
* Sets the length of the body component.
this.length = Math.max(length, 0);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
@Override
public boolean allowsChildren() {
return true;
}
-
- /**
- * Accept a visitor to this BodyComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this BodyComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
-
+
}
package net.sf.openrocket.rocketcomponent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-
/**
* Rocket body tube component. Has only two parameters, a radius and length.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial {
private static final Translator trans = Application.getTranslator();
-
+
private double radius = 0;
private boolean autoRadius = false; // Radius chosen automatically based on parent component
/**
* Return the outer radius of the body tube.
- *
- * @return the outside radius of the tube
+ *
+ * @return the outside radius of the tube
*/
- @Override
- public double getOuterRadius () {
+ @Override
+ public double getOuterRadius() {
if (autoRadius) {
// Return auto radius from front or rear
double r = -1;
* Set the outer radius of the body tube. If the radius is less than the wall thickness,
* the wall thickness is decreased accordingly of the value of the radius.
* This method sets the automatic radius off.
- *
- * @param radius the outside radius in standard units
+ *
+ * @param radius the outside radius in standard units
*/
- @Override
- public void setOuterRadius (double radius) {
+ @Override
+ public void setOuterRadius(double radius) {
if ((this.radius == radius) && (autoRadius == false))
return;
}
/**
- * Sets whether the radius is selected automatically or not.
+ * Sets whether the radius is selected automatically or not.
*/
public void setRadiusAutomatic(boolean auto) {
if (autoRadius == auto)
@Override
- public double getAftRadius() { return getOuterRadius(); }
+ public double getAftRadius() {
+ return getOuterRadius();
+ }
+
@Override
- public double getForeRadius() { return getOuterRadius(); }
+ public double getForeRadius() {
+ return getOuterRadius();
+ }
+
@Override
public boolean isAftRadiusAutomatic() {
return isRadiusAutomatic();
-
-
-
-
- @Override
+
+ @Override
public double getInnerRadius() {
if (filled)
return 0;
- return Math.max(getOuterRadius()-thickness, 0);
+ return Math.max(getOuterRadius() - thickness, 0);
}
- @Override
+ @Override
public void setInnerRadius(double r) {
- setThickness(getOuterRadius()-r);
+ setThickness(getOuterRadius() - r);
}
//// Body tube
return trans.get("BodyTube.BodyTube");
}
-
- /**
- * Accept a visitor to this BodyTube in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this BodyTube
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
-
+
+
/************ Component calculations ***********/
// From SymmetricComponent
if (filled)
return 0.0;
else
- return Math.max(getOuterRadius()-thickness,0);
+ return Math.max(getOuterRadius() - thickness, 0);
}
@Override
public double getLongitudinalUnitInertia() {
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
- return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) +
- MathUtil.pow2(getLength())) / 12;
+ return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getLength())) / 12;
}
@Override
public double getRotationalUnitInertia() {
// 1/2 * (r1^2 + r2^2)
- return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius()))/2;
+ return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius())) / 2;
}
/**
* Adds bounding coordinates to the given set. The body tube will fit within the
* convex hull of the points.
- *
+ *
* Currently the points are simply a rectangular box around the body tube.
*/
@Override
/**
* Check whether the given type can be added to this component. BodyTubes allow any
* InternalComponents or ExternalComponents, excluding BodyComponents, to be added.
- *
+ *
* @param type The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
/*
* (non-Javadoc)
* Copy the motor and ejection delay HashMaps.
- *
+ *
* @see rocketcomponent.RocketComponent#copy()
*/
@SuppressWarnings("unchecked")
public class CenteringRing extends RadiusRingComponent {
-
+
public CenteringRing() {
setOuterRadiusAutomatic(true);
setInnerRadiusAutomatic(true);
setLength(0.002);
}
-
-
+
+
@Override
public double getInnerRadius() {
// Implement sibling inner radius automation
*/
if (!(sibling instanceof InnerTube)) // Excludes itself
continue;
-
+
double pos1 = this.toRelative(Coordinate.NUL, sibling)[0].x;
double pos2 = this.toRelative(new Coordinate(getLength()), sibling)[0].x;
if (pos2 < 0 || pos1 > sibling.getLength())
continue;
-
+
innerRadius = Math.max(innerRadius, ((InnerTube) sibling).getOuterRadius());
}
innerRadius = Math.min(innerRadius, getOuterRadius());
}
}
-
+
return super.getInnerRadius();
}
-
-
+
+
@Override
public void setOuterRadiusAutomatic(boolean auto) {
super.setOuterRadiusAutomatic(auto);
}
-
+
@Override
public void setInnerRadiusAutomatic(boolean auto) {
super.setInnerRadiusAutomatic(auto);
}
-
+
@Override
public String getComponentName() {
return "Centering ring";
}
-
+
@Override
public boolean allowsChildren() {
return false;
}
-
+
@Override
public boolean isCompatible(Class<? extends RocketComponent> type) {
return false;
}
-
- /**
- * Accept a visitor to this CenteringRing in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this CenteringRing
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
}
+++ /dev/null
-/*
- * ComponentVisitor.java
- */
-package net.sf.openrocket.rocketcomponent;
-
-/**
- * This class implements a Visitor pattern to visit any/all components of a Rocket.
- */
-public class ComponentVisitor implements Visitor<ComponentVisitor, RocketComponent> {
-
- /**
- * The delegate.
- */
- private ComponentVisitorStrategy strategy;
-
- /**
- * Constructor.
- *
- * @param aStrategy the object to delegate the visiting to
- */
- public ComponentVisitor (ComponentVisitorStrategy aStrategy) {
- strategy = aStrategy;
- strategy.setParent(this);
- }
-
- /**
- * Visit a Rocket object.
- *
- * @param visitable the Rocket to visit
- */
- public void visit (final Rocket visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a RocketComponent object. This is used to catch any RocketComponent subclass not explicity defined by a
- * visit method in this strategy.
- *
- * @param visitable the RocketComponent to visit
- */
- public void visit (final RocketComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a Stage object.
- *
- * @param visitable the Stage to visit
- */
- public void visit (final Stage visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit an ExternalComponent object.
- *
- * @param visitable the ExternalComponent to visit
- */
- public void visit (final ExternalComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a BodyComponent object.
- *
- * @param visitable the BodyComponent to visit
- */
- public void visit (final BodyComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a RingComponent object.
- *
- * @param visitable the RingComponent to visit
- */
- public void visit (final RingComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit an InnerTube object.
- *
- * @param visitable the InnerTube to visit
- */
- public void visit (final InnerTube visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a LaunchLug object.
- *
- * @param visitable the LaunchLug to visit
- */
- public void visit (final LaunchLug visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a Transition object.
- *
- * @param visitable the Transition to visit
- */
- public void visit (final Transition visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a RadiusRingComponent object.
- *
- * @param visitable the RadiusRingComponent to visit
- */
- public void visit (final RadiusRingComponent visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a MassObject object.
- *
- * @param visitable the MassObject to visit
- */
- public void visit (final MassObject visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a NoseCone object.
- *
- * @param visitable the NoseCone to visit
- */
- public void visit (final NoseCone visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a BodyTube object.
- *
- * @param visitable the BodyTube to visit
- */
- public void visit (final BodyTube visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a Rocket object.
- *
- * @param visitable the Rocket to visit
- */
- public void visit (final TrapezoidFinSet visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a Rocket object.
- *
- * @param visitable the Rocket to visit
- */
- public void visit (final EllipticalFinSet visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Visit a FreeformFinSet object.
- *
- * @param visitable the FreeformFinSet to visit
- */
- public void visit (final FreeformFinSet visitable) {
- strategy.visit(visitable);
- }
-
- /**
- * Perform any cleanup or finishing operations.
- */
- public void close () {
- strategy.close();
- }
-
-}
+++ /dev/null
-/*
- * ComponentVisitorStrategy.java
- */
-package net.sf.openrocket.rocketcomponent;
-
-/**
- * This interface defines the methods used in a Rocket component visitor. By using a strategy, we can reuse one visitor
- * definition and just instrument it with different strategies.
- */
-public interface ComponentVisitorStrategy {
-
- /**
- * Visit a Rocket object.
- *
- * @param visitable the Rocket to visit
- */
- void visit (final Rocket visitable);
-
- /**
- * Visit a RocketComponent object. This is used to catch any RocketComponent subclass not explicity defined by a
- * visit method in this strategy.
- *
- * @param visitable the RocketComponent to visit
- */
- void visit (final RocketComponent visitable);
-
- /**
- * Visit a Stage object.
- *
- * @param visitable the Stage to visit
- */
- void visit (final Stage visitable);
-
- /**
- * Visit an ExternalComponent object.
- *
- * @param visitable the ExternalComponent to visit
- */
- void visit (final ExternalComponent visitable);
-
- /**
- * Visit a BodyComponent object.
- *
- * @param visitable the BodyComponent to visit
- */
- void visit (final BodyComponent visitable);
-
- /**
- * Visit a RingComponent object.
- *
- * @param visitable the RingComponent to visit
- */
- void visit (final RingComponent visitable);
-
- /**
- * Visit an InnerTube object.
- *
- * @param visitable the InnerTube to visit
- */
- void visit (final InnerTube visitable);
-
- /**
- * Visit a LaunchLug object.
- *
- * @param visitable the LaunchLug to visit
- */
- void visit (final LaunchLug visitable);
-
- /**
- * Visit a Transition object.
- *
- * @param visitable the Transition to visit
- */
- void visit (final Transition visitable);
-
- /**
- * Visit a RadiusRingComponent object.
- *
- * @param visitable the RadiusRingComponent to visit
- */
- void visit (final RadiusRingComponent visitable);
-
- /**
- * Visit a MassComponent object.
- *
- * @param visitable the MassComponent to visit
- */
- void visit (final MassObject visitable);
-
- /**
- * Visit a NoseCone object.
- *
- * @param visitable the NoseCone to visit
- */
- void visit (final NoseCone visitable);
-
- /**
- * Visit a BodyTube object.
- *
- * @param visitable the BodyTube to visit
- */
- void visit (final BodyTube visitable);
-
- /**
- * Visit a TrapezoidFinSet object.
- *
- * @param visitable the TrapezoidFinSet to visit
- */
- void visit (final TrapezoidFinSet visitable);
-
- /**
- * Visit an EllipticalFinSet object.
- *
- * @param visitable the EllipticalFinSet to visit
- */
- void visit (final EllipticalFinSet visitable);
-
- /**
- * Visit a FreeformFinSet object.
- *
- * @param visitable the FreeformFinSet to visit
- */
- void visit (final FreeformFinSet visitable);
-
- /**
- * Set the visitor that is using this strategy.
- *
- * @param parent the visitor
- */
- void setParent (ComponentVisitor parent);
-
- /**
- * Perform any cleanup or finishing operations.
- */
- void close ();
-}
public class EllipticalFinSet extends FinSet {
public static final int POINTS = 21;
-
+
private static final double[] POINT_X = new double[POINTS];
private static final double[] POINT_Y = new double[POINTS];
static {
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() {
Coordinate[] points = new Coordinate[POINTS];
public String getComponentName() {
return "Elliptical fin set";
}
-
-
+
+
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;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
- /**
- * Accept a visitor to this EllipticalFinSet in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this EllipticalFinSet
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
}
package net.sf.openrocket.rocketcomponent;
-import java.util.List;
-
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Prefs;
+import java.util.List;
+
/**
* Class of components with well-defined physical appearance and which have an effect on
- * aerodynamic simulation. They have material defined for them, and the mass of the component
+ * aerodynamic simulation. They have material defined for them, and the mass of the component
* is calculated using the component's volume.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class ExternalComponent extends RocketComponent {
-
+
public enum Finish {
//// Rough
ROUGH("ExternalComponent.Rough", 500e-6),
SMOOTH("ExternalComponent.Smoothpaint", 20e-6),
//// Polished
POLISHED("ExternalComponent.Polished", 2e-6);
-
+
private final String name;
private final double roughnessSize;
-
+
Finish(String name, double roughness) {
this.name = name;
this.roughnessSize = roughness;
}
-
+
public double getRoughnessSize() {
return roughnessSize;
}
-
+
@Override
public String toString() {
final Translator trans = Application.getTranslator();
return trans.get(name) + " (" + UnitGroup.UNITS_ROUGHNESS.toStringUnit(roughnessSize) + ")";
}
}
-
-
+
+
/**
* The material of the component.
*/
protected Material material = null;
-
+
protected Finish finish = Finish.NORMAL;
-
-
+
+
/**
* Constructor that sets the relative position of the component.
super(relativePosition);
this.material = Prefs.getDefaultComponentMaterial(this.getClass(), Material.Type.BULK);
}
-
+
/**
* Returns the volume of the component. This value is used in calculating the mass
* of the object.
*/
public abstract double getComponentVolume();
-
+
/**
* Calculates the mass of the component as the product of the volume and interior density.
*/
public double getComponentMass() {
return material.getDensity() * getComponentVolume();
}
-
+
/**
* ExternalComponent has aerodynamic effect, so return true.
*/
public boolean isAerodynamic() {
return true;
}
-
+
/**
* ExternalComponent has effect on the mass, so return true.
*/
public boolean isMassive() {
return true;
}
-
-
+
+
public Material getMaterial() {
return material;
}
-
+
public void setMaterial(Material mat) {
if (mat.getType() != Material.Type.BULK) {
throw new IllegalArgumentException("ExternalComponent requires a bulk material" +
" type=" + mat.getType());
}
-
+
if (material.equals(mat))
return;
material = mat;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public Finish getFinish() {
return finish;
}
-
+
public void setFinish(Finish finish) {
if (this.finish == finish)
return;
this.finish = finish;
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
-
-
+
@Override
protected List<RocketComponent> copyFrom(RocketComponent c) {
this.material = src.material;
return super.copyFrom(c);
}
-
- /**
- * Accept a visitor to this ExternalComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this ExternalComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
}
public class FreeformFinSet extends FinSet {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
private ArrayList<Coordinate> points = new ArrayList<Coordinate>();
public FreeformFinSet() {
public FreeformFinSet(FinSet finset) {
Coordinate[] finpoints = finset.getFinPoints();
this.copyFrom(finset);
-
+
points.clear();
for (Coordinate c: finpoints) {
points.add(c);
* inserted in its stead.
* <p>
* The specified fin set should not be used after the call!
- *
+ *
* @param finset the fin set to convert.
* @return the new freeform fin set.
*/
/**
* Add a fin point between indices <code>index-1</code> and <code>index</code>.
* The point is placed at the midpoint of the current segment.
- *
+ *
* @param index the fin point before which to add the new point.
*/
public void addPoint(int index) {
* Remove the fin point with the given index. The first and last fin points
* cannot be removed, and will cause an <code>IllegalFinPointException</code>
* if attempted.
- *
+ *
* @param index the fin point index to remove
* @throws IllegalFinPointException if removing would result in invalid fin planform
*/
* Set the point at position <code>i</code> to coordinates (x,y).
* <p>
* Note that this method enforces basic fin shape restrictions (non-negative y,
- * first and last point locations) silently, but throws an
+ * first and last point locations) silently, but throws an
* <code>IllegalFinPointException</code> if the point causes fin segments to
* intersect.
* <p>
* Moving of the first point in the X-axis is allowed, but this actually moves
* all of the other points the corresponding distance back.
- *
+ *
* @param index the point index to modify.
* @param x the x-coordinate.
* @param y the y-coordinate.
return c;
}
- /**
- * Accept a visitor to this FreeformFinSet in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this FreeformFinSet
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
- private void validate(ArrayList<Coordinate> points) throws IllegalFinPointException {
- final int n = points.size();
- if (points.get(0).x != 0 || points.get(0).y != 0 ||
- points.get(n - 1).x < 0 || points.get(n - 1).y != 0) {
+ private void validate(ArrayList<Coordinate> pts) throws IllegalFinPointException {
+ final int n = pts.size();
+ if (pts.get(0).x != 0 || pts.get(0).y != 0 ||
+ pts.get(n - 1).x < 0 || pts.get(n - 1).y != 0) {
throw new IllegalFinPointException("Start or end point illegal.");
}
for (int i = 0; i < n - 1; i++) {
for (int j = i + 2; j < n - 1; j++) {
- if (intersects(points.get(i).x, points.get(i).y, points.get(i + 1).x, points.get(i + 1).y,
- points.get(j).x, points.get(j).y, points.get(j + 1).x, points.get(j + 1).y)) {
+ if (intersects(pts.get(i).x, pts.get(i).y, pts.get(i + 1).x, pts.get(i + 1).y,
+ pts.get(j).x, pts.get(j).y, pts.get(j + 1).x, pts.get(j + 1).y)) {
throw new IllegalFinPointException("segments intersect");
}
}
- if (points.get(i).z != 0) {
+ if (pts.get(i).z != 0) {
throw new IllegalFinPointException("z-coordinate not zero");
}
}
package net.sf.openrocket.rocketcomponent;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
/**
* This class defines an inner tube that can be used as a motor mount. The component
* may also be clustered.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class InnerTube extends ThicknessRingComponent
implements Clusterable, RadialParent, MotorMount {
private static final Translator trans = Application.getTranslator();
-
+
private ClusterConfiguration cluster = ClusterConfiguration.SINGLE;
private double clusterScale = 1.0;
private double clusterRotation = 0.0;
* Get the current cluster configuration.
* @return The current cluster configuration.
*/
+ @Override
public ClusterConfiguration getClusterConfiguration() {
return cluster;
}
* Set the current cluster configuration.
* @param cluster The cluster configuration.
*/
+ @Override
public void setClusterConfiguration(ClusterConfiguration cluster) {
this.cluster = cluster;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
return new Coordinate(this.getLength() - motor.getLength() + this.getMotorOverhang());
}
- /**
- * Accept a visitor to an InnerTube object in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this InnerTube
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
-
-
/*
* (non-Javadoc)
* Copy the motor and ejection delay HashMaps.
- *
+ *
* @see rocketcomponent.RocketComponent#copy()
*/
@SuppressWarnings("unchecked")
private double radius;
private double thickness;
-
+
private double radialDirection = 0;
-
+
/* These are calculated when the component is first attached to any Rocket */
private double shiftY, shiftZ;
-
-
+
+
public LaunchLug() {
super(Position.MIDDLE);
thickness = 0.001;
length = 0.03;
}
-
-
+
+
public double getOuterRadius () {
return radius;
}
-
+
public void setOuterRadius (double radius) {
if (MathUtil.equals(this.radius, radius))
return;
this.thickness = Math.min(this.thickness, this.radius);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public double getInnerRadius() {
return radius - thickness;
}
-
+
public void setInnerRadius(double innerRadius) {
setOuterRadius(innerRadius + thickness);
}
-
+
public double getThickness() {
return thickness;
}
-
+
public void setThickness(double thickness) {
if (MathUtil.equals(this.thickness, thickness))
return;
this.thickness = MathUtil.clamp(thickness, 0, radius);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
public double getRadialDirection() {
return radialDirection;
}
-
+
public void setRadialDirection(double direction) {
direction = MathUtil.reduce180(direction);
if (MathUtil.equals(this.radialDirection, direction))
this.radialDirection = direction;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
public void setLength(double length) {
if (MathUtil.equals(this.length, length))
this.length = length;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
super.setRelativePosition(position);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
@Override
public void setPositionValue(double value) {
super.setPositionValue(value);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
-
+
+
@Override
public Coordinate[] shiftCoordinates(Coordinate[] array) {
array = super.shiftCoordinates(array);
-
+
for (int i = 0; i < array.length; i++) {
array[i] = array[i].add(0, shiftY, shiftZ);
}
-
+
return array;
}
-
-
+
+
@Override
public void componentChanged(ComponentChangeEvent e) {
super.componentChanged(e);
-
- /*
+
+ /*
* shiftY and shiftZ must be computed here since calculating them
* in shiftCoordinates() would cause an infinite loop due to .toRelative
*/
RocketComponent body;
double parentRadius;
-
+
for (body = this.getParent(); body != null; body = body.getParent()) {
if (body instanceof SymmetricComponent)
break;
}
-
+
if (body == null) {
parentRadius = 0;
} else {
x2 = MathUtil.clamp(x2, 0, body.getLength());
parentRadius = Math.max(s.getRadius(x1), s.getRadius(x2));
}
-
+
shiftY = Math.cos(radialDirection) * (parentRadius + radius);
shiftZ = Math.sin(radialDirection) * (parentRadius + radius);
-
+
// System.out.println("Computed shift: y="+shiftY+" z="+shiftZ);
}
-
-
+
+
@Override
public double getComponentVolume() {
return length * Math.PI * (MathUtil.pow2(radius) - MathUtil.pow2(radius - thickness));
}
-
+
@Override
public Collection<Coordinate> getComponentBounds() {
ArrayList<Coordinate> set = new ArrayList<Coordinate>();
addBound(set, length, radius);
return set;
}
-
+
@Override
public Coordinate getComponentCG() {
return new Coordinate(length / 2, 0, 0, getComponentMass());
}
-
+
@Override
public String getComponentName() {
//// Launch lug
return trans.get("LaunchLug.Launchlug");
}
-
+
@Override
public double getLongitudinalUnitInertia() {
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) +
MathUtil.pow2(getLength())) / 12;
}
-
+
@Override
public double getRotationalUnitInertia() {
// 1/2 * (r1^2 + r2^2)
return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius()))/2;
}
-
+
@Override
public boolean allowsChildren() {
return false;
}
-
+
@Override
public boolean isCompatible(Class<? extends RocketComponent> type) {
// Allow nothing to be attached to a LaunchLug
return false;
}
-
- /**
- * Accept a visitor to this LaunchLug in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this LaunchLug
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
}
package net.sf.openrocket.rocketcomponent;
-import static net.sf.openrocket.util.MathUtil.pow2;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
import java.util.ArrayList;
import java.util.Collection;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
+import static net.sf.openrocket.util.MathUtil.pow2;
/**
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class MassObject extends InternalComponent {
-
+
private double radius;
-
+
private double radialPosition;
private double radialDirection;
-
+
private double shiftY = 0;
private double shiftZ = 0;
-
-
+
+
public MassObject() {
this(0.03, 0.015);
}
-
+
public MassObject(double length, double radius) {
super();
-
+
this.length = length;
this.radius = radius;
-
+
this.setRelativePosition(Position.TOP);
this.setPositionValue(0.0);
}
-
-
+
+
public void setLength(double length) {
length = Math.max(length, 0);
if (MathUtil.equals(this.length, length)) {
this.length = length;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
public final double getRadius() {
return radius;
}
-
-
+
+
public final void setRadius(double radius) {
radius = Math.max(radius, 0);
if (MathUtil.equals(this.radius, radius)) {
this.radius = radius;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
public final double getRadialPosition() {
return radialPosition;
}
-
+
public final void setRadialPosition(double radialPosition) {
radialPosition = Math.max(radialPosition, 0);
if (MathUtil.equals(this.radialPosition, radialPosition)) {
shiftZ = radialPosition * Math.sin(radialDirection);
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public final double getRadialDirection() {
return radialDirection;
}
-
+
public final void setRadialDirection(double radialDirection) {
radialDirection = MathUtil.reduce180(radialDirection);
if (MathUtil.equals(this.radialDirection, radialDirection)) {
shiftZ = radialPosition * Math.sin(radialDirection);
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
/**
* Shift the coordinates according to the radial position and direction.
*/
}
return array;
}
-
+
@Override
public final Coordinate getComponentCG() {
return new Coordinate(length / 2, shiftY, shiftZ, getComponentMass());
}
-
+
@Override
public final double getLongitudinalUnitInertia() {
return (3 * pow2(radius) + pow2(length)) / 12;
}
-
+
@Override
public final double getRotationalUnitInertia() {
return pow2(radius) / 2;
}
-
+
@Override
public final Collection<Coordinate> getComponentBounds() {
Collection<Coordinate> c = new ArrayList<Coordinate>();
addBound(c, length, radius);
return c;
}
-
- /**
- * Accept a visitor to this MassObject in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this MassObject
- */
- @Override
- public void accept(final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
+
}
/**
* Rocket nose cones of various types. Implemented as a transition with the
* fore radius == 0.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class NoseCone extends Transition {
private static final Translator trans = Application.getTranslator();
-
+
/********* Constructors **********/
public NoseCone() {
- this(Transition.Shape.OGIVE, 6*DEFAULT_RADIUS, DEFAULT_RADIUS);
+ this(Transition.Shape.OGIVE, 6 * DEFAULT_RADIUS, DEFAULT_RADIUS);
}
public NoseCone(Transition.Shape type, double length, double radius) {
super.setForeRadiusAutomatic(false);
super.setForeRadius(0);
super.setForeShoulderLength(0);
- super.setForeShoulderRadius(0.9*radius);
+ super.setForeShoulderRadius(0.9 * radius);
super.setForeShoulderThickness(0);
super.setForeShoulderCapped(filled);
super.setThickness(0.002);
}
-
+
/********** Get/set methods for component parameters **********/
-
+
@Override
public double getForeRadius() {
return 0;
public void setForeRadius(double r) {
// No-op
}
-
+
@Override
public boolean isForeRadiusAutomatic() {
return false;
public void setForeRadiusAutomatic(boolean b) {
// No-op
}
-
+
@Override
public double getForeShoulderLength() {
return 0;
}
-
+
@Override
public double getForeShoulderRadius() {
return 0;
}
-
+
@Override
public double getForeShoulderThickness() {
return 0;
}
-
+
@Override
public boolean isForeShoulderCapped() {
return false;
}
-
+
@Override
public void setForeShoulderCapped(boolean capped) {
// No-op
}
-
+
@Override
public void setForeShoulderLength(double foreShoulderLength) {
// No-op
}
-
+
@Override
public void setForeShoulderRadius(double foreShoulderRadius) {
// No-op
}
-
+
@Override
public void setForeShoulderThickness(double foreShoulderThickness) {
// No-op
}
-
+
@Override
public boolean isClipped() {
return false;
// No-op
}
-
- /********** RocketComponent methods **********/
+ /********** RocketComponent methods **********/
+
/**
* Return component name.
*/
//// Nose cone
return trans.get("NoseCone.NoseCone");
}
-
- /**
- * Accept a visitor to this NoseCone in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this NoseCone
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
+
}
/**
* An inner component that consists of a hollow cylindrical component. This can be
* an inner tube, tube coupler, centering ring, bulkhead etc.
- *
+ *
* The properties include the inner and outer radii, length and radial position.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class RadiusRingComponent extends RingComponent implements Coaxial {
protected double outerRadius = 0;
protected double innerRadius = 0;
-
+
@Override
public double getOuterRadius() {
if (outerRadiusAutomatic && getParent() instanceof RadialParent) {
outerRadius = Math.min(((RadialParent)parent).getInnerRadius(pos1),
((RadialParent)parent).getInnerRadius(pos2));
}
-
+
return outerRadius;
}
r = Math.max(r,0);
if (MathUtil.equals(outerRadius, r) && !isOuterRadiusAutomatic())
return;
-
+
outerRadius = r;
outerRadiusAutomatic = false;
if (getInnerRadius() > r) {
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
@Override
public double getInnerRadius() {
return innerRadius;
r = Math.max(r,0);
if (MathUtil.equals(innerRadius, r))
return;
-
+
innerRadius = r;
innerRadiusAutomatic = false;
if (getOuterRadius() < r) {
outerRadius = r;
outerRadiusAutomatic = false;
}
-
+
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
@Override
public double getThickness() {
return Math.max(getOuterRadius() - getInnerRadius(), 0);
@Override
public void setThickness(double thickness) {
double outer = getOuterRadius();
-
+
thickness = MathUtil.clamp(thickness, 0, outer);
setInnerRadius(outer - thickness);
}
- /**
- * Accept a visitor to this RadiusRingComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this RadiusRingComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
}
/**
* An inner component that consists of a hollow cylindrical component. This can be
* an inner tube, tube coupler, centering ring, bulkhead etc.
- *
+ *
* The properties include the inner and outer radii, length and radial position.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public abstract class RingComponent extends StructuralComponent implements Coaxial {
protected boolean outerRadiusAutomatic = false;
protected boolean innerRadiusAutomatic = false;
-
-
+
+
private double radialDirection = 0;
private double radialPosition = 0;
-
+
private double shiftY = 0;
private double shiftZ = 0;
-
-
+
+
@Override
public abstract double getOuterRadius();
@Override
public abstract void setOuterRadius(double r);
-
+
@Override
- public abstract double getInnerRadius();
+ public abstract double getInnerRadius();
@Override
public abstract void setInnerRadius(double r);
-
+
@Override
public abstract double getThickness();
public abstract void setThickness(double thickness);
-
-
+
+
public final boolean isOuterRadiusAutomatic() {
return outerRadiusAutomatic;
}
-
+
protected void setOuterRadiusAutomatic(boolean auto) {
if (auto == outerRadiusAutomatic)
return;
outerRadiusAutomatic = auto;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
+
+
public final boolean isInnerRadiusAutomatic() {
return innerRadiusAutomatic;
}
-
+
protected void setInnerRadiusAutomatic(boolean auto) {
if (auto == innerRadiusAutomatic)
return;
innerRadiusAutomatic = auto;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
-
-
+
+
+
+
public final void setLength(double length) {
double l = Math.max(length,0);
if (this.length == l)
return;
-
+
this.length = l;
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
/**
* Return the radial direction of displacement of the component. Direction 0
* is equivalent to the Y-direction.
- *
+ *
* @return the radial direction.
*/
public double getRadialDirection() {
return radialDirection;
}
-
+
/**
* Set the radial direction of displacement of the component. Direction 0
* is equivalent to the Y-direction.
- *
+ *
* @param dir the radial direction.
*/
public void setRadialDirection(double dir) {
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
-
-
+
+
+
/**
* Return the radial position of the component. The position is the distance
* of the center of the component from the center of the parent component.
- *
+ *
* @return the radial position.
*/
public double getRadialPosition() {
return radialPosition;
}
-
+
/**
* Set the radial position of the component. The position is the distance
* of the center of the component from the center of the parent component.
- *
+ *
* @param pos the radial position.
*/
public void setRadialPosition(double pos) {
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
-
+
public double getRadialShiftY() {
return shiftY;
}
-
+
public double getRadialShiftZ() {
return shiftZ;
}
-
+
public void setRadialShift(double y, double z) {
radialPosition = Math.hypot(y, z);
radialDirection = Math.atan2(z, y);
-
- // Re-calculate to ensure consistency
+
+ // Re-calculate to ensure consistency
shiftY = radialPosition * Math.cos(radialDirection);
shiftZ = radialPosition * Math.sin(radialDirection);
assert(MathUtil.equals(y, shiftY));
assert(MathUtil.equals(z, shiftZ));
-
+
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
}
return ((Clusterable)this).getClusterConfiguration().getClusterCount();
return 1;
}
-
-
+
+
/**
* Shift the coordinates according to the radial position and direction.
*/
}
return array;
}
-
-
+
+
@Override
public Collection<Coordinate> getComponentBounds() {
List<Coordinate> bounds = new ArrayList<Coordinate>();
addBound(bounds,length,getOuterRadius());
return bounds;
}
-
-
+
+
@Override
public Coordinate getComponentCG() {
return new Coordinate(length/2, 0, 0, getComponentMass());
return ringMass(getOuterRadius(), getInnerRadius(), getLength(),
getMaterial().getDensity()) * getClusterCount();
}
-
+
@Override
public double getLongitudinalUnitInertia() {
return ringRotationalUnitInertia(getOuterRadius(), getInnerRadius());
}
- /**
- * Accept a visitor to this RingComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this RingComponent
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
}
* (eg. the rocket listener lists) and the methods defined in RocketComponent call these.
* It also defines some other methods that concern the whole rocket, and helper methods
* that keep information about the program state.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
/**
* Return the number of stages in this rocket.
- *
+ *
* @return the number of stages in this rocket.
*/
public int getStageCount() {
/**
* Return the non-negative modification ID of this rocket. The ID is changed
- * every time any change occurs in the rocket. This can be used to check
+ * every time any change occurs in the rocket. This can be used to check
* whether it is necessary to void cached data in cases where listeners can not
* or should not be used.
* <p>
* Three other modification IDs are also available, {@link #getMassModID()},
- * {@link #getAerodynamicModID()} {@link #getTreeModID()}, which change every time
- * a mass change, aerodynamic change, or tree change occur. Even though the values
- * of the different modification ID's may be equal, they should be treated totally
+ * {@link #getAerodynamicModID()} {@link #getTreeModID()}, which change every time
+ * a mass change, aerodynamic change, or tree change occur. Even though the values
+ * of the different modification ID's may be equal, they should be treated totally
* separate.
* <p>
* Note that undo events restore the modification IDs that were in use at the
* corresponding undo level. Subsequent modifications, however, produce modIDs
* distinct from those already used.
- *
+ *
* @return a unique ID number for this modification state.
*/
public int getModID() {
/**
* Return the non-negative mass modification ID of this rocket. See
* {@link #getModID()} for details.
- *
+ *
* @return a unique ID number for this mass-modification state.
*/
public int getMassModID() {
/**
* Return the non-negative aerodynamic modification ID of this rocket. See
* {@link #getModID()} for details.
- *
+ *
* @return a unique ID number for this aerodynamic-modification state.
*/
public int getAerodynamicModID() {
/**
* Return the non-negative tree modification ID of this rocket. See
* {@link #getModID()} for details.
- *
+ *
* @return a unique ID number for this tree-modification state.
*/
public int getTreeModID() {
/**
* Return the non-negative functional modificationID of this rocket.
* This changes every time a functional change occurs.
- *
+ *
* @return a unique ID number for this functional modification state.
*/
public int getFunctionalModID() {
/**
* Set whether the rocket has a perfect finish. This will affect whether the
* boundary layer is assumed to be fully turbulent or not.
- *
+ *
* @param perfectFinish whether the finish is perfect.
*/
public void setPerfectFinish(boolean perfectFinish) {
/**
* Get whether the rocket has a perfect finish.
- *
+ *
* @return the perfectFinish
*/
public boolean isPerfectFinish() {
* Freezes the rocket structure from firing any events. This may be performed to
* combine several actions on the structure into a single large action.
* <code>thaw()</code> must always be called afterwards.
- *
+ *
* NOTE: Always use a try/finally to ensure <code>thaw()</code> is called:
* <pre>
* Rocket r = c.getRocket();
* r.thaw();
* }
* </pre>
- *
+ *
* @see #thaw()
*/
public void freeze() {
* Return the default configuration. This should be used in the user interface
* to ensure a consistent rocket configuration between dialogs. It should NOT
* be used in simulations not relating to the UI.
- *
+ *
* @return the default {@link Configuration}.
*/
public Configuration getDefaultConfiguration() {
/**
* Return an array of the motor configuration IDs. This array is guaranteed
* to contain the <code>null</code> ID as the first element.
- *
+ *
* @return an array of the motor configuration IDs.
*/
public String[] getMotorConfigurationIDs() {
/**
* Add a new motor configuration ID to the motor configurations. The new ID
* is returned.
- *
+ *
* @return the new motor configuration ID.
*/
public String newMotorConfigurationID() {
/**
* Add a specified motor configuration ID to the motor configurations.
- *
+ *
* @param id the motor configuration ID.
* @return true if successful, false if the ID was already used.
*/
/**
* Remove a motor configuration ID from the configuration IDs. The <code>null</code>
* ID cannot be removed, and an attempt to remove it will be silently ignored.
- *
+ *
* @param id the motor configuration ID to remove
*/
public void removeMotorConfigurationID(String id) {
/**
* Check whether <code>id</code> is a valid motor configuration ID.
- *
+ *
* @param id the configuration ID.
* @return whether a motor configuration with that ID exists.
*/
/**
* Check whether the given motor configuration ID has motors defined for it.
- *
+ *
* @param id the motor configuration ID (may be invalid).
* @return whether any motors are defined for it.
*/
/**
* Return the user-set name of the motor configuration. If no name has been set,
* returns an empty string (not null).
- *
+ *
* @param id the motor configuration id
* @return the configuration name
*/
/**
* Set the name of the motor configuration. A name can be unset by passing
* <code>null</code> or an empty string.
- *
+ *
* @param id the motor configuration id
* @param name the name for the motor configuration
*/
/**
- * Return either the motor configuration name (if set) or its description.
- *
+ * Return either the motor configuration name (if set) or its description.
+ *
* @param id the motor configuration ID.
* @return a textual representation of the configuration
*/
}
/**
- * Return a description for the motor configuration, generated from the motor
+ * Return a description for the motor configuration, generated from the motor
* designations of the components.
- *
+ *
* @param id the motor configuration ID.
* @return a textual representation of the configuration
*/
return trans.get("Rocket.motorCount.Nomotor");
}
- // Change multiple occurrences of a motor to n x motor
+ // Change multiple occurrences of a motor to n x motor
List<String> stages = new ArrayList<String>();
for (List<String> stage : list) {
return (Stage.class.isAssignableFrom(type));
}
- /**
- * Accept a visitor to this Rocket in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this Rocket
- */
- @Override
- public void accept(final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
}
import net.sf.openrocket.util.UniqueID;
-public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent>,
- Visitable<ComponentVisitor, RocketComponent> {
+public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent> {
private static final LogHelper log = Application.getLogger();
private static final Translator trans = Application.getTranslator();
-
+
/*
* Text is suitable to the form
* Position relative to: <title>
/**
- * Return the longitudinal (around the y- or z-axis) unitary moment of inertia.
+ * Return the longitudinal (around the y- or z-axis) unitary moment of inertia.
* The unitary moment of inertia is the moment of inertia with the assumption that
* the mass of the component is one kilogram. The inertia is measured in
* respect to the non-overridden CG.
- *
+ *
* @return the longitudinal unitary moment of inertia of this component.
*/
public abstract double getLongitudinalUnitInertia();
/**
- * Return the rotational (around the x-axis) unitary moment of inertia.
+ * Return the rotational (around the x-axis) unitary moment of inertia.
* The unitary moment of inertia is the moment of inertia with the assumption that
* the mass of the component is one kilogram. The inertia is measured in
* respect to the non-overridden CG.
- *
+ *
* @return the rotational unitary moment of inertia of this component.
*/
public abstract double getRotationalUnitInertia();
* Test whether this component allows any children components. This method must
* return true if and only if {@link #isCompatible(Class)} returns true for any
* rocket component class.
- *
+ *
* @return <code>true</code> if children can be attached to this component, <code>false</code> otherwise.
*/
public abstract boolean allowsChildren();
* is enforced by the <code>addChild()</code> methods. The return value of this method
* may change to reflect the current state of this component (e.g. two components of some
* type cannot be placed as children).
- *
+ *
* @param type The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
/**
* Test whether the given component can be added to this component. This is equivalent
* to calling <code>isCompatible(c.getClass())</code>.
- *
+ *
* @param c Component to test.
* @return Whether the component can be added.
* @see #isCompatible(Class)
/**
* Return a collection of bounding coordinates. The coordinates must be such that
* the component is fully enclosed in their convex hull.
- *
+ *
* @return a collection of coordinates that bound the component.
*/
public abstract Collection<Coordinate> getComponentBounds();
* coordinate for each cluster.
* <p>
* The default implementation simply returns the array, and thus produces no shift.
- *
+ *
* @param c an array of coordinates to shift.
* @return an array of shifted coordinates. The method may modify the contents
* of the passed array and return the array itself.
/**
- * Called when any component in the tree fires a ComponentChangeEvent. This is by
- * default a no-op, but subclasses may override this method to e.g. invalidate
- * cached data. The overriding method *must* call
+ * Called when any component in the tree fires a ComponentChangeEvent. This is by
+ * default a no-op, but subclasses may override this method to e.g. invalidate
+ * cached data. The overriding method *must* call
* <code>super.componentChanged(e)</code> at some point.
- *
+ *
* @param e The event fired
*/
protected void componentChanged(ComponentChangeEvent e) {
/**
* Return the user-provided name of the component, or the component base
* name if the user-provided name is empty. This can be used in the UI.
- *
+ *
* @return A string describing the component.
*/
@Override
* Make a deep copy of the rocket component tree structure from this component
* downwards for copying purposes. Each component in the copy will be assigned
* a new component ID, making it a safe copy. This method does not fire any events.
- *
+ *
* @return A deep copy of the structure.
*/
public final RocketComponent copy() {
* undo/redo mechanism. This method should not be used for other purposes,
* such as copy/paste. This method does not fire any events.
* <p>
- * This method must be overridden by any component that refers to mutable objects,
+ * This method must be overridden by any component that refers to mutable objects,
* or if some fields should not be copied. This should be performed by
* <code>RocketComponent c = super.copyWithOriginalID();</code> and then cloning/modifying
* the appropriate fields.
* <p>
* This is not performed as serializing/deserializing for performance reasons.
- *
+ *
* @return A deep copy of the structure.
*/
protected RocketComponent copyWithOriginalID() {
}
- /**
- * Accept a visitor to this RocketComponent in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this RocketComponent
- */
- @Override
- public void accept(final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
////////////// Methods that may not be overridden ////////////
}
/**
- * Set the color of the object to use in 2D figures.
+ * Set the color of the object to use in 2D figures.
*/
public final void setColor(Color c) {
if ((color == null && c == null) ||
/**
* Get the current override mass. The mass is not necessarily in use
* at the moment.
- *
+ *
* @return the override mass
*/
public final double getOverrideMass() {
/**
* Set the current override mass. The mass is not set to use by this
* method.
- *
+ *
* @param m the override mass
*/
public final void setOverrideMass(double m) {
/**
* Return whether mass override is active for this component. This does NOT
* take into account whether a parent component is overriding the mass.
- *
+ *
* @return whether the mass is overridden
*/
public final boolean isMassOverridden() {
/**
* Set whether the mass is currently overridden.
- *
+ *
* @param o whether the mass is overridden
*/
public final void setMassOverridden(boolean o) {
/**
* Return the current override CG. The CG is not necessarily overridden.
- *
+ *
* @return the override CG
*/
public final Coordinate getOverrideCG() {
/**
* Return the x-coordinate of the current override CG.
- *
+ *
* @return the x-coordinate of the override CG.
*/
public final double getOverrideCGX() {
/**
* Set the current override CG to (x,0,0).
- *
+ *
* @param x the x-coordinate of the override CG to set.
*/
public final void setOverrideCGX(double x) {
/**
* Return whether the CG is currently overridden.
- *
+ *
* @return whether the CG is overridden
*/
public final boolean isCGOverridden() {
/**
* Set whether the CG is currently overridden.
- *
+ *
* @param o whether the CG is overridden
*/
public final void setCGOverridden(boolean o) {
* always or never overrides subcomponents. In this case the subclass should
* also override {@link #isOverrideSubcomponentsEnabled()} to return
* <code>false</code>.
- *
+ *
* @return whether the current mass and/or CG override overrides subcomponents as well.
*/
public boolean getOverrideSubcomponents() {
/**
* Set whether the mass and/or CG override overrides all subcomponent values
* as well. See {@link #getOverrideSubcomponents()} for details.
- *
+ *
* @param override whether the mass and/or CG override overrides all subcomponent.
*/
public void setOverrideSubcomponents(boolean override) {
* <p>
* This method may be overridden if the setting of overriding subcomponents
* cannot be set.
- *
+ *
* @return whether the option to override subcomponents is currently enabled.
*/
public boolean isOverrideSubcomponentsEnabled() {
/**
* Return the comment of the component. The component may contain multiple lines
* using \n as a newline separator.
- *
+ *
* @return the comment of the component.
*/
public final String getComment() {
/**
* Set the comment of the component.
- *
+ *
* @param comment the comment of the component.
*/
public final void setComment(String comment) {
/**
* Returns the unique ID of the component.
- *
+ *
* @return the ID of the component.
*/
public final String getID() {
* Get the characteristic length of the component, for example the length of a body tube
* of the length of the root chord of a fin. This is used in positioning the component
* relative to its parent.
- *
+ *
* If the length of a component is settable, the class must define the setter method
* itself.
*/
* do not support setting the relative position. A component that does support
* it should override this with a public method that simply calls this
* supermethod AND fire a suitable ComponentChangeEvent.
- *
+ *
* @param position the relative positioning.
*/
protected void setRelativePosition(RocketComponent.Position position) {
/**
* Get the position value of the component. The exact meaning of the value is
* dependent on the current relative positioning.
- *
+ *
* @return the positional value.
*/
public final double getPositionValue() {
* do not support setting the relative position. A component that does support
* it should override this with a public method that simply calls this
* supermethod AND fire a suitable ComponentChangeEvent.
- *
+ *
* @param value the position value of the component.
*/
public void setPositionValue(double value) {
/**
- * Return coordinate <code>c</code> described in the coordinate system of
+ * Return coordinate <code>c</code> described in the coordinate system of
* <code>dest</code>. If <code>dest</code> is <code>null</code> returns
* absolute coordinates.
* <p>
* This method returns an array of coordinates, each of which represents a
* position of the coordinate in clustered cases. The array is guaranteed
- * to contain at least one element.
+ * to contain at least one element.
* <p>
* The current implementation does not support rotating components.
- *
+ *
* @param c Coordinate in the component's coordinate system.
* @param dest Destination component coordinate system.
* @return an array of coordinates describing <code>c</code> in coordinates
}
// Check whether destination has been found or whether to backtrack
- // TODO: LOW: Backtracking into clustered components uses only one component
+ // TODO: LOW: Backtracking into clustered components uses only one component
if ((dest != null) && (component != dest)) {
Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
for (int i = 0; i < array.length; i++) {
/**
- * Recursively sum the lengths of all subcomponents that have position
+ * Recursively sum the lengths of all subcomponents that have position
* Position.AFTER.
- *
+ *
* @return Sum of the lengths.
*/
private final double getTotalLength() {
/**
* Return the (possibly overridden) mass of component.
- *
+ *
* @return The mass of the component or the given override mass.
*/
public final double getMass() {
/**
* Return the (possibly overridden) center of gravity and mass.
- *
+ *
* Returns the CG with the weight of the coordinate set to the weight of the component.
* Both CG and mass may be separately overridden.
- *
+ *
* @return The CG of the component or the given override CG.
*/
public final Coordinate getCG() {
* Return the longitudinal (around the y- or z-axis) moment of inertia of this component.
* The moment of inertia is scaled in reference to the (possibly overridden) mass
* and is relative to the non-overridden CG.
- *
+ *
* @return the longitudinal moment of inertia of this component.
*/
public final double getLongitudinalInertia() {
* Return the rotational (around the y- or z-axis) moment of inertia of this component.
* The moment of inertia is scaled in reference to the (possibly overridden) mass
* and is relative to the non-overridden CG.
- *
+ *
* @return the rotational moment of inertia of this component.
*/
public final double getRotationalInertia() {
/**
* Adds a child to the rocket component tree. The component is added to the end
- * of the component's child list. This is a helper method that calls
+ * of the component's child list. This is a helper method that calls
* {@link #addChild(RocketComponent,int)}.
- *
+ *
* @param component The component to add.
- * @throws IllegalArgumentException if the component is already part of some
+ * @throws IllegalArgumentException if the component is already part of some
* component tree.
* @see #addChild(RocketComponent,int)
*/
/**
- * Adds a child to the rocket component tree. The component is added to
+ * Adds a child to the rocket component tree. The component is added to
* the given position of the component's child list.
* <p>
- * This method may be overridden to enforce more strict component addition rules.
+ * This method may be overridden to enforce more strict component addition rules.
* The tests should be performed first and then this method called.
- *
+ *
* @param component The component to add.
* @param index Position to add component to.
- * @throws IllegalArgumentException If the component is already part of
+ * @throws IllegalArgumentException If the component is already part of
* some component tree.
*/
public void addChild(RocketComponent component, int index) {
/**
* Removes a child from the rocket component tree.
- *
+ *
* @param n remove the n'th child.
* @throws IndexOutOfBoundsException if n is out of bounds
*/
/**
* Removes a child from the rocket component tree. Does nothing if the component
* is not present as a child.
- *
+ *
* @param component the component to remove
* @return whether the component was a child
*/
/**
* Move a child to another position.
- *
+ *
* @param component the component to move
* @param index the component's new position
* @throws IllegalArgumentException If an illegal placement was attempted.
/**
* Returns the position of the child in this components child list, or -1 if the
* component is not a child of this component.
- *
+ *
* @param child The child to search for.
* @return Position in the list or -1 if not found.
*/
/**
* Get the parent component of this component. Returns <code>null</code> if the component
* has no parent.
- *
+ *
* @return The parent of this component or <code>null</code>.
*/
public final RocketComponent getParent() {
/**
* Get the root component of the component tree.
- *
+ *
* @return The root component of the component tree.
*/
public final RocketComponent getRoot() {
}
/**
- * Returns the root Rocket component of this component tree. Throws an
+ * Returns the root Rocket component of this component tree. Throws an
* IllegalStateException if the root component is not a Rocket.
- *
+ *
* @return The root Rocket component of the component tree.
* @throws IllegalStateException If the root component is not a Rocket.
*/
/**
* Return the Stage component that this component belongs to. Throws an
* IllegalStateException if a Stage is not in the parentage of this component.
- *
+ *
* @return The Stage component this component belongs to.
* @throws IllegalStateException if a Stage component is not in the parentage.
*/
/**
* Return the stage number of the stage this component belongs to. The stages
* are numbered from zero upwards.
- *
+ *
* @return the stage number this component belongs to.
*/
public final int getStageNumber() {
* Find a component with the given ID. The component tree is searched from this component
* down (including this component) for the ID and the corresponding component is returned,
* or null if not found.
- *
+ *
* @param idToFind ID to search for.
* @return The component with the ID, or null if not found.
*/
* Adds a ComponentChangeListener to the rocket tree. The listener is added to the root
* component, which must be of type Rocket (which overrides this method). Events of all
* subcomponents are sent to all listeners.
- *
+ *
* @throws IllegalStateException - if the root component is not a Rocket
*/
public void addComponentChangeListener(ComponentChangeListener l) {
* the root component, which must be of type Rocket (which overrides this method).
* Does nothing if the root component is not a Rocket. (The asymmetry is so
* that listeners can always be removed just in case.)
- *
+ *
* @param l Listener to remove
*/
public void removeComponentChangeListener(ComponentChangeListener l) {
/**
- * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
- * <code>addComponentChangeListener()</code> except that it uses a
+ * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
+ * <code>addComponentChangeListener()</code> except that it uses a
* <code>ChangeListener</code>. The same events are dispatched to the
- * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
+ * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
* of <code>ChangeEvent</code>.
- *
+ *
* @throws IllegalStateException - if the root component is not a <code>Rocket</code>
*/
@Override
* removeComponentChangeListener() except it uses a ChangeListener.
* Does nothing if the root component is not a Rocket. (The asymmetry is so
* that listeners can always be removed just in case.)
- *
+ *
* @param l Listener to remove
*/
@Override
/**
- * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
+ * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
* root component, which must be of type Rocket (which overrides this method).
* Events of all subcomponents are sent to all listeners.
- *
- * If the component tree root is not a Rocket, the event is ignored. This is the
- * case when constructing components not in any Rocket tree. In this case it
+ *
+ * If the component tree root is not a Rocket, the event is ignored. This is the
+ * case when constructing components not in any Rocket tree. In this case it
* would be impossible for the component to have listeners in any case.
- *
+ *
* @param e Event to send
*/
protected void fireComponentChangeEvent(ComponentChangeEvent e) {
/**
* Fires a ComponentChangeEvent of the given type. The source of the event is set to
* this component.
- *
+ *
* @param type Type of event
* @see #fireComponentChangeEvent(ComponentChangeEvent)
*/
* This is a safety check that in-place replaced components are no longer used.
* All non-trivial methods (with the exception of methods simply getting a property)
* should call this method before changing or computing anything.
- *
+ *
* @throws BugException if this component has been invalidated by {@link #copyFrom(RocketComponent)}.
*/
protected void checkState() {
* <p>
* If an iterator iterating over only the direct children of the component is required,
* use <code>component.getChildren().iterator()</code>.
- *
+ *
* TODO: HIGH: Remove this after merges have been done
- *
- * @param returnSelf boolean value specifying whether the component itself should be
+ *
+ * @param returnSelf boolean value specifying whether the component itself should be
* returned
* @return An iterator for the children and sub-children.
* @deprecated Use {@link #iterator(boolean)} instead
* Returns an iterator that iterates over all children and sub-children, including itself.
* <p>
* This method is equivalent to <code>deepIterator(true)</code>.
- *
+ *
* TODO: HIGH: Remove this after merges have been done
- *
+ *
* @return An iterator for this component, its children and sub-children.
* @deprecated Use {@link #iterator()} instead
*/
* <p>
* If an iterator iterating over only the direct children of the component is required,
* use <code>component.getChildren().iterator()</code>.
- *
- * @param returnSelf boolean value specifying whether the component itself should be
+ *
+ * @param returnSelf boolean value specifying whether the component itself should be
* returned
* @return An iterator for the children and sub-children.
*/
* Returns an iterator that iterates over this components, its children and sub-children.
* <p>
* This method is equivalent to <code>iterator(true)</code>.
- *
+ *
* @return An iterator for this component, its children and sub-children.
*/
@Override
/**
* Helper method to add rotationally symmetric bounds at the specified coordinates.
* The X-axis value is <code>x</code> and the radius at the specified position is
- * <code>r</code>.
+ * <code>r</code>.
*/
protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
bounds.add(new Coordinate(x, -r, -r));
* to them have been removed (for example by firing appropriate events). The list contains
* all children and sub-children of the current component and the entire component
* tree of <code>src</code>.
- *
+ *
* @return a list of components that should not be used after this call.
*/
protected List<RocketComponent> copyFrom(RocketComponent src) {
/**
* Private inner class to implement the Iterator.
- *
+ *
* This iterator is fail-fast if the root of the structure is a Rocket.
*/
private static class RocketComponentIterator implements Iterator<RocketComponent> {
import net.sf.openrocket.startup.Application;
public class Stage extends ComponentAssembly {
-
+
private static final Translator trans = Application.getTranslator();
@Override
//// Stage
return trans.get("Stage.Stage");
}
-
-
+
+
@Override
public boolean allowsChildren() {
return true;
}
-
+
/**
- * Check whether the given type can be added to this component. A Stage allows
+ * Check whether the given type can be added to this component. A Stage allows
* only BodyComponents to be added.
*
* @param type The RocketComponent class type to add.
public boolean isCompatible (Class<? extends RocketComponent> type) {
return BodyComponent.class.isAssignableFrom(type);
}
-
- /**
- * Accept a visitor to this Stage in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this Stage
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
}
package net.sf.openrocket.rocketcomponent;
+import static java.lang.Math.*;
+import static net.sf.openrocket.util.MathUtil.*;
+
+import java.util.Collection;
+
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
-import java.util.Collection;
-
-import static java.lang.Math.sin;
-import static java.lang.Math.sqrt;
-import static net.sf.openrocket.util.Chars.FRAC12;
-import static net.sf.openrocket.util.Chars.FRAC34;
-import static net.sf.openrocket.util.MathUtil.pow2;
-import static net.sf.openrocket.util.MathUtil.pow3;
-
public class Transition extends SymmetricComponent {
+ private static final Translator trans = Application.getTranslator();
private static final double CLIP_PRECISION = 0.0001;
private double aftShoulderThickness;
private double aftShoulderLength;
private boolean aftShoulderCapped;
- private static final Translator trans = Application.getTranslator();
+
// Used to cache the clip length
private double clipLength = -1;
clipLength = -1;
}
- /**
- * Accept a visitor to this Transition in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this Transition
- */
- @Override
- public void accept (final ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
-
/**
* Check whether the given type can be added to this component. Transitions allow any
* InternalComponents to be added.
- *
- * @param type The RocketComponent class type to add.
+ *
+ * @param ctype The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
@Override
- public boolean isCompatible(Class<? extends RocketComponent> type) {
- if (InternalComponent.class.isAssignableFrom(type))
+ public boolean isCompatible(Class<? extends RocketComponent> ctype) {
+ if (InternalComponent.class.isAssignableFrom(ctype))
return true;
return false;
}
/**
* An enumeration listing the possible shapes of transitions.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public static enum Shape {
return sqrt(2 * radius * x - x * x); // radius/length * sphere
}
},
+
//// Power series
POWER(trans.get("Shape.Powerseries"),
- //// A power series nose cone has a profile of <i>Radius</i> × (<i>x</i> / <i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter. For <i>k</i>=0.5 this is a <b>
- trans.get("Shape.Powerseries.descA1") + FRAC12 +
- //// -power</b> or <b>parabolic</b> nose cone, for <i>k</i>=0.75 a <b>
- trans.get("Shape.Powerseries.descA2") + FRAC34 +
- //// -power</b>, and for <i>k</i>=1 a <b>conical</b> nose cone.
- trans.get("Shape.Powerseries.descA3"),
- //// A power series transition has a profile of <i>Radius</i> × (<i>x</i> / <i>Length</i>)<sup><i>k</i></sup> where <i>k</i> is the shape parameter. For <i>k</i>=0.5 the transition is <b>
- trans.get("Shape.Powerseries.descB1") + FRAC12 +
- //// -power</b> or <b>parabolic</b>, for <i>k</i>=0.75 a <b>
- trans.get("Shape.Powerseries.descB2") + FRAC34 +
- //// -power</b>, and for <i>k</i>=1 <b>conical</b>.
- trans.get("Shape.Powerseries.descB3"), true) {
+ trans.get("Shape.Powerseries.desc1"),
+ trans.get("Shape.Powerseries.desc2"), true) {
@Override
public boolean usesParameter() { // Range 0...1
return true;
}
},
+
//// Parabolic series
PARABOLIC(trans.get("Shape.Parabolicseries"),
////A parabolic series nose cone has a profile of a parabola. The shape parameter defines the segment of the parabola to utilize. The shape parameter 1.0 produces a <b>full parabola</b> which is tangent to the body tube, 0.75 produces a <b>3/4 parabola</b>, 0.5 procudes a <b>1/2 parabola</b> and 0 produces a <b>conical</b> nose cone.
// In principle a parabolic transition is clippable, but the difference is
// negligible.
-
+
@Override
public boolean usesParameter() { // Range 0...1
return true;
}
},
-
//// Haack series
HAACK(trans.get("Shape.Haackseries"),
//// The Haack series nose cones are designed to minimize drag. The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> nose cone, which minimizes drag for fixed length and diameter, while a value of 0.333 produces an <b>LV-Haack</b> nose cone, which minimizes drag for fixed length and volume.
trans.get("Shape.Haackseries.desc1"),
//// The Haack series <i>nose cones</i> are designed to minimize drag. These transition shapes are their equivalents, but do not necessarily produce optimal drag for transitions. The shape parameter 0 produces an <b>LD-Haack</b> or <b>Von Karman</b> shape, while a value of 0.333 produces an <b>LV-Haack</b> shape.
trans.get("Shape.Haackseries.desc2"), true) {
+
@Override
public boolean usesParameter() {
return true;
* shape parameter at the point x from the tip of the component. It is assumed
* that the fore radius if zero and the aft radius is <code>radius >= 0</code>.
* Boattails are achieved by reversing the component.
- *
+ *
* @param x Position from the tip of the component.
* @param radius Aft end radius >= 0.
* @param length Length of the transition >= 0.
/**
* A set of trapezoidal fins. The root and tip chords are perpendicular to the rocket
* base line, while the leading and aft edges may be slanted.
- *
+ *
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
* sweep tipChord
* | |___________
* | / |
- * | / |
+ * | / |
* | / | height
* / |
* __________/________________|_____________
* length
* == rootChord
*/
-
+
// rootChord == length
private double tipChord = 0;
private double height = 0;
private double sweep = 0;
-
-
+
+
public TrapezoidFinSet() {
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,
+ 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 &&
length = Math.max(r,0);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
public double getTipChord() {
return tipChord;
}
sweep = r;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
-
+
/**
* Get the sweep angle. This is calculated from the true sweep and height, and is not
* stored separetely.
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;
}
-
+
/**
* Returns the span of a trapezoidal fin.
*/
public double getSpan() {
return height;
}
-
+
@Override
public String getComponentName() {
return trans.get("TrapezoidFinSet.TrapezoidFinSet");
}
- /**
- * Accept a visitor to this TrapezoidFinSet in the component hierarchy.
- *
- * @param theVisitor the visitor that will be called back with a reference to this TrapezoidFinSet
- */
- @Override
- public void accept(ComponentVisitor theVisitor) {
- theVisitor.visit(this);
- }
}
package net.sf.openrocket.startup;
import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
+import net.sf.openrocket.l10n.ClassBasedTranslator;
import net.sf.openrocket.l10n.DebugTranslator;
+import net.sf.openrocket.l10n.ExceptionSuppressingTranslator;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.logging.LogLevel;
private static LogHelper logger;
private static LogLevelBufferLogger logBuffer;
- private static Translator translator = new DebugTranslator();
+ private static Translator baseTranslator = new DebugTranslator(null);
private static ThrustCurveMotorSetDatabase motorSetDatabase;
* @return a translator.
*/
public static Translator getTranslator() {
- return translator;
+ Translator t = baseTranslator;
+ t = new ClassBasedTranslator(t, 1);
+ t = new ExceptionSuppressingTranslator(t);
+ return t;
}
/**
* Set the translator used in obtaining translated strings.
* @param translator the translator to set.
*/
- public static void setTranslator(Translator translator) {
- Application.translator = translator;
+ public static void setBaseTranslator(Translator translator) {
+ Application.baseTranslator = translator;
}
} else {
l = new Locale(split[0], split[1], split[2]);
}
+ log.info("Setting custom locale " + l);
Locale.setDefault(l);
}
Translator t;
+ t = new ResourceBundleTranslator("l10n.messages");
if (Locale.getDefault().getLanguage().equals("xx")) {
- t = new DebugTranslator();
- } else {
- t = new ResourceBundleTranslator("l10n.messages");
+ t = new DebugTranslator(t);
}
log.info("Set up translation for locale " + Locale.getDefault() +
", debug.currentFile=" + t.get("debug.currentFile"));
- Application.setTranslator(t);
+ Application.setBaseTranslator(t);
}
--- /dev/null
+package net.sf.openrocket.util;
+
+import java.util.List;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.startup.Application;
+
+/**
+ * Abstract implementation of a ChangeSource.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public abstract class AbstractChangeSource implements ChangeSource {
+ private static final LogHelper log = Application.getLogger();
+
+ private final List<ChangeListener> listeners = new ArrayList<ChangeListener>();
+
+ private final ChangeEvent event = new ChangeEvent(this);
+
+
+ @Override
+ public final void addChangeListener(ChangeListener listener) {
+ listeners.add(listener);
+ log.verbose(1, "Adding change listeners, listener count is now " + listeners.size());
+ }
+
+ @Override
+ public final void removeChangeListener(ChangeListener listener) {
+ listeners.remove(listener);
+ log.verbose(1, "Removing change listeners, listener count is now " + listeners.size());
+ }
+
+
+ /**
+ * Fire a change event to all listeners.
+ */
+ protected void fireChangeEvent() {
+ ChangeListener[] array = listeners.toArray(new ChangeListener[0]);
+
+ for (ChangeListener l : array) {
+ l.stateChanged(event);
+ }
+ }
+}
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.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.material.Material;
*/
private static class DefaultMaterialHolder {
private static final Translator trans = Application.getTranslator();
-
+
//// Elastic cord (round 2mm, 1/16 in)
private static final Material DEFAULT_LINE_MATERIAL =
Databases.findMaterial(Material.Type.LINE, trans.get("Databases.materials.Elasticcordround2mm"),
storeVersion();
}
+
+ /**
+ * Retrieve an enum value from the user preferences.
+ *
+ * @param <T> the enum type
+ * @param key the key
+ * @param def the default value, cannot be null
+ * @return the value in the preferences, or the default value
+ */
+ public static <T extends Enum<T>> T getEnum(String key, T def) {
+ if (def == null) {
+ throw new BugException("Default value cannot be null");
+ }
+
+ String value = getString(key, null);
+ if (value == null) {
+ return def;
+ }
+
+ try {
+ return Enum.valueOf(def.getDeclaringClass(), value);
+ } catch (IllegalArgumentException e) {
+ return def;
+ }
+ }
+
+ /**
+ * Store an enum value to the user preferences.
+ *
+ * @param key the key
+ * @param value the value to store, or null to remove the value
+ */
+ public static void putEnum(String key, Enum<?> value) {
+ if (value == null) {
+ putString(key, null);
+ } else {
+ putString(key, value.name());
+ }
+ }
+
+
/**
* Return a boolean preference.
*
if (color == null)
return Color.BLACK;
+ Color clr = parseColor(color);
+ if (clr != null) {
+ return clr;
+ } else {
+ return Color.BLACK;
+ }
+ }
+
+ public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) {
+ if (color == null)
+ return;
+ set("componentColors", c, stringifyColor(color));
+ }
+
+
+ private static Color parseColor(String color) {
+ if (color == null) {
+ return null;
+ }
+
String[] rgb = color.split(",");
if (rgb.length == 3) {
try {
} catch (NumberFormatException ignore) {
}
}
-
- return Color.BLACK;
+ return null;
}
- public static void setDefaultColor(Class<? extends RocketComponent> c, Color color) {
- if (color == null)
- return;
+
+ private static String stringifyColor(Color color) {
String string = color.getRed() + "," + color.getGreen() + "," + color.getBlue();
- set("componentColors", c, string);
+ return string;
}
+
+
public static Color getMotorBorderColor() {
// TODO: MEDIUM: Motor color (settable?)
return new Color(0, 0, 0, 200);
}
+ //// Printing
+
+ public static PrintSettings getPrintSettings() {
+ PrintSettings settings = new PrintSettings();
+ Color c;
+
+ c = parseColor(getString("print.template.fillColor", null));
+ if (c != null) {
+ settings.setTemplateFillColor(c);
+ }
+
+ c = parseColor(getString("print.template.borderColor", null));
+ if (c != null) {
+ settings.setTemplateBorderColor(c);
+ }
+
+ settings.setPaperSize(getEnum("print.paper.size", settings.getPaperSize()));
+ settings.setPaperOrientation(getEnum("print.paper.orientation", settings.getPaperOrientation()));
+
+ return settings;
+ }
+
+ public static void setPrintSettings(PrintSettings settings) {
+ putString("print.template.fillColor", stringifyColor(settings.getTemplateFillColor()));
+ putString("print.template.borderColor", stringifyColor(settings.getTemplateBorderColor()));
+ putEnum("print.paper.size", settings.getPaperSize());
+ putEnum("print.paper.orientation", settings.getPaperOrientation());
+ }
+
//// Background flight data computation
public static boolean computeFlightInBackground() {
--- /dev/null
+package net.sf.openrocket.gui.print;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class TestPaperSize {
+
+ @Test
+ public void testGetDefaultForCountry() {
+ assertEquals(PaperSize.LETTER, PaperSize.getDefaultForCountry("US"));
+ assertEquals(PaperSize.LETTER, PaperSize.getDefaultForCountry("cA"));
+ assertEquals(PaperSize.LETTER, PaperSize.getDefaultForCountry("mx"));
+
+ assertEquals(PaperSize.A4, PaperSize.getDefaultForCountry("FI"));
+ assertEquals(PaperSize.A4, PaperSize.getDefaultForCountry("xy"));
+
+ assertNull(PaperSize.getDefaultForCountry("FIN"));
+ assertNull(PaperSize.getDefaultForCountry("a"));
+ assertNull(PaperSize.getDefaultForCountry("A4"));
+ assertNull(PaperSize.getDefaultForCountry(null));
+ }
+
+ @Test
+ public void testGetSizeFromString() {
+ assertEquals(PaperSize.LETTER, PaperSize.getSizeFromString("Letter"));
+ assertEquals(PaperSize.LEGAL, PaperSize.getSizeFromString(" legal\t"));
+ assertEquals(PaperSize.A4, PaperSize.getSizeFromString(" A4\n"));
+ assertEquals(PaperSize.A3, PaperSize.getSizeFromString("A3"));
+
+ assertNull(PaperSize.getSizeFromString("#A4"));
+ assertNull(PaperSize.getSizeFromString(""));
+ assertNull(PaperSize.getSizeFromString(null));
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import static org.junit.Assert.*;
+
+import java.util.MissingResourceException;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.auto.Mock;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(JMock.class)
+public class TestClassBasedTranslator {
+ Mockery context = new JUnit4Mockery();
+
+ @Mock
+ Translator translator;
+
+ @Test
+ public void testClassName() {
+ ClassBasedTranslator cbt = new ClassBasedTranslator(null, 0);
+ assertEquals("TestClassBasedTranslator", cbt.getClassName());
+
+ cbt = new ClassBasedTranslator(null, "foobar");
+ assertEquals("foobar", cbt.getClassName());
+ }
+
+ @Test
+ public void testGetWithClassName() {
+ ClassBasedTranslator cbt = new ClassBasedTranslator(translator, 0);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("TestClassBasedTranslator.fake.key"); will(returnValue("foobar"));
+ }});
+ // @formatter:on
+
+ assertEquals("foobar", cbt.get("fake.key"));
+ }
+
+
+ @Test
+ public void testGetWithoutClassName() {
+ ClassBasedTranslator cbt = new ClassBasedTranslator(translator, 0);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("TestClassBasedTranslator.fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ oneOf(translator).get("fake.key"); will(returnValue("barbaz"));
+ }});
+ // @formatter:on
+
+ assertEquals("barbaz", cbt.get("fake.key"));
+ }
+
+
+ @Test
+ public void testMissing() {
+ ClassBasedTranslator cbt = new ClassBasedTranslator(translator, 0);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("TestClassBasedTranslator.fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ oneOf(translator).get("fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ }});
+ // @formatter:on
+
+ try {
+ fail("Returned: " + cbt.get("fake.key"));
+ } catch (MissingResourceException e) {
+ assertEquals("Neither key 'TestClassBasedTranslator.fake.key' nor 'fake.key' could be found", e.getMessage());
+ }
+
+ }
+}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.MissingResourceException;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.auto.Mock;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(JMock.class)
+public class TestExceptionSuppressingTranslator {
+ Mockery context = new JUnit4Mockery();
+
+ @Mock
+ Translator translator;
+
+ @Test
+ public void testSuccessful() {
+ ExceptionSuppressingTranslator est = new ExceptionSuppressingTranslator(translator);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("fake.key"); will(returnValue("foobar"));
+ }});
+ // @formatter:on
+
+ assertEquals("foobar", est.get("fake.key"));
+ }
+
+
+ @Test
+ public void testFailure() {
+ ExceptionSuppressingTranslator est = new ExceptionSuppressingTranslator(translator);
+
+ assertEquals("Prerequisite failed", 0, ExceptionSuppressingTranslator.failures);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ oneOf(translator).get("fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ oneOf(translator).get("fake.key2"); will(throwException(new MissingResourceException("a", "b", "c")));
+ }});
+ // @formatter:on
+
+ // Test first failure
+ assertEquals("fake.key", est.get("fake.key"));
+ assertEquals(1, ExceptionSuppressingTranslator.failures);
+
+ // Test second failure
+ assertEquals("fake.key", est.get("fake.key"));
+ assertEquals(1, ExceptionSuppressingTranslator.failures);
+
+ // Test failure with other key
+ assertEquals("fake.key2", est.get("fake.key2"));
+ assertEquals(2, ExceptionSuppressingTranslator.failures);
+ }
+
+
+}
$version = $_GET["version"];
$updates = "";
-$unstable = "1.1.3";
+$unstable = "1.1.5";
$stable = "1.0.0";
-if (preg_match("/^1\.1\.[12]/", $version)) {
+if (preg_match("/^1\.1\.4/", $version)) {
$updates = "Version: " . $unstable . "\n" .
+ "5: Fixes to printing system";
+} else if (preg_match("/^1\.1\.3/", $version)) {
+ $updates = "Version: " . $unstable . "\n" .
+ "5: Initial printing support\n" .
+ "4: Bug fixes\n";
+} else if (preg_match("/^1\.1\.[12]/", $version)) {
+ $updates = "Version: " . $unstable . "\n" .
+ "6: Initial printing support\n" .
"5: Initial drag-and-drop support\n" .
"4: Bug fixes\n";
} else if (preg_match("/^1\.1\.0/", $version)) {
$updates = "Version: " . $unstable . "\n" .
+ "6: Initial printing support\n" .
"6: Enhanced motor selection\n" .
"5: Rewritten simulation code\n" .
"5: Drag-and-drop support\n" .
<div class="content">
<div class="news">
<h2>Recent news:</h2>
+ <p><span class="date">10.6.2011:</span> Version 1.1.5 is
+ <a href="download.html">released</a>!</p>
+ <p>This release includes updates to the printing system which fixes
+ printing on Windows.</p>
+ <p><span class="date">5.3.2011:</span> Version 1.1.4 is
+ <a href="download.html">released</a>!</p>
+ <p>This release includes initial printing support, thanks to Doug
+ Pedrick. Printing still has a few issues on some platforms.
+ Various bugs have also been fixed.</p>
<p><span class="date">6.10.2010:</span> Version 1.1.3 is
<a href="download.html">released</a>!</p>
<p>This release includes support for moving and copying components
startup times than previous releases.</p>
<p>Simulation listeners written for older versions of OpenRocket
are not compatible with this release.</p>
- <p><span class="date">21.3.2010:</span> Version 1.1.0 is
- <a href="download.html">released</a>!</p>
- <p>This release includes the first support for loading RockSim
- rocket design files (.RKT), thanks to contributions by Doug
- Pedrick. It's also the kick-off of the 1.1 development branch of
- OpenRocket.</p>
</div>
<div class="contentholder">
<h2>Ready packages</h2>
<a href="http://sourceforge.net/donate/index.php?group_id=260357"><img src="project-support.jpg" width="88" height="32" alt="Support This Project" /></a>
</div>
<div class="downloadbox">
- <a class="main" href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.3/OpenRocket-1.1.3.jar/download">
+ <a class="main" href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.5/OpenRocket-1.1.5.jar/download">
<strong>Download now!</strong>
- <span>OpenRocket-1.1.3.jar</span>
+ <span>OpenRocket-1.1.5.jar</span>
</a>
<span class="alternative">
- <a href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.3/ReleaseNotes/view">Release notes</a> |
- <a href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.3/OpenRocket-1.1.3-src.zip/download">Source code</a>
+ <a href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.5/ReleaseNotes/view">Release notes</a> |
+ <a href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.5/OpenRocket-1.1.5-src.zip/download">Source code</a>
</span>
</div>
<h3>Stable release</h3>
Windows) by double-clicking the package icon. No installation is
required.</p>
<p>From the command line OpenRocket can be started by
- <span class="command">java -jar OpenRocket-1.1.3.jar</span></p>
+ <span class="command">java -jar OpenRocket-1.1.5.jar</span></p>
</div>
<div class="clear"></div>
<h2>Introduction</h2>
<div class="rightpane">
<div class="downloadbox">
- <a class="main" href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.3/OpenRocket-1.1.3.jar/download">
+ <a class="main" href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.5/OpenRocket-1.1.5.jar/download">
<strong>Download now!</strong>
- <span>OpenRocket-1.1.3.jar</span>
+ <span>OpenRocket-1.1.5.jar</span>
</a>
<span class="alternative">
- <a href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.3/ReleaseNotes/view">Release notes</a> |
+ <a href="https://sourceforge.net/projects/openrocket/files/openrocket/1.1.5/ReleaseNotes/view">Release notes</a> |
<a href="download.html">Other versions</a>
</span>
</div>
<div class="clear"></div>
<div class="news">
<h2>News</h2>
+ <p><span class="date">10.6.2011:</span> Version 1.1.5 is
+ <a href="download.html">released</a>!</p>
+ <p>This release includes updates to the printing system which fixes
+ printing on Windows.</p>
+ <p><span class="date">5.3.2011:</span> Version 1.1.4 is
+ <a href="download.html">released</a>!</p>
+ <p>This release includes initial printing support, thanks to Doug
+ Pedrick. Printing still has a few issues on some platforms.
+ Various bugs have also been fixed.</p>
<p><span class="date">6.10.2010:</span> Version 1.1.3 is
<a href="download.html">released</a>!</p>
<p>This release includes support for moving and copying components
<set stableversion="1.0.0">
-<set developmentversion="1.1.3">
+<set developmentversion="1.1.5">
<set version="${developmentversion}">
<def name="downloadbox">
<!--- Remember to move the position of "onlyrecent" below! --->
+ <p><span class="date">10.6.2011:</span> Version 1.1.5 is
+ <a href="download.html">released</a>!</p>
+
+ <p>This release includes updates to the printing system which fixes
+ printing on Windows.</p>
+
+ <p><span class="date">5.3.2011:</span> Version 1.1.4 is
+ <a href="download.html">released</a>!</p>
+
+ <p>This release includes initial printing support, thanks to Doug
+ Pedrick. Printing still has a few issues on some platforms.
+ Various bugs have also been fixed.</p>
+
<p><span class="date">6.10.2010:</span> Version 1.1.3 is
<a href="download.html">released</a>!</p>
<p>Simulation listeners written for older versions of OpenRocket
are not compatible with this release.</p>
+
+ <if not onlyrecent><!--- Older items not shown on download page: --->
+
+
<p><span class="date">21.3.2010:</span> Version 1.1.0 is
<a href="download.html">released</a>!</p>
Pedrick. It's also the kick-off of the 1.1 development branch of
OpenRocket.</p>
-
- <if not onlyrecent><!--- Older items not shown on download page: --->
-
-
<p><span class="date">10.3.2010:</span> Version 1.0.0 is
<a href="download.html">released</a>!</p>