</accessrules>
</classpathentry>
<classpathentry kind="lib" path="lib/miglayout15-swing.jar"/>
- <classpathentry kind="lib" path="lib-test/hamcrest-core-1.3.0RC1.jar"/>
- <classpathentry kind="lib" path="lib-test/hamcrest-library-1.3.0RC1.jar"/>
- <classpathentry kind="lib" path="lib-test/jmock-2.6.0-RC2.jar"/>
- <classpathentry kind="lib" path="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
- <classpathentry kind="lib" path="lib-test/junit-dep-4.8.2.jar"/>
+ <classpathentry kind="lib" path="lib/iText-5.0.2.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
super(component);
LaunchLug lug = (LaunchLug)component;
- double ld = lug.getLength() / (2*lug.getRadius());
+ double ld = lug.getLength() / (2*lug.getOuterRadius());
CDmul = Math.max(1.3 - ld, 1);
- refArea = Math.PI * MathUtil.pow2(lug.getRadius()) -
+ refArea = Math.PI * MathUtil.pow2(lug.getOuterRadius()) -
Math.PI * MathUtil.pow2(lug.getInnerRadius()) * Math.max(1 - ld, 0);
}
package net.sf.openrocket.file.openrocket;
-import java.awt.Color;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.database.Databases;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.document.StorageOptions;
import net.sf.openrocket.document.Simulation.Status;
+import net.sf.openrocket.document.StorageOptions;
import net.sf.openrocket.file.RocketLoadException;
import net.sf.openrocket.file.RocketLoader;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
import net.sf.openrocket.rocketcomponent.EngineBlock;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.rocketcomponent.RingComponent;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
import net.sf.openrocket.rocketcomponent.ShockCord;
import net.sf.openrocket.rocketcomponent.Stage;
import net.sf.openrocket.rocketcomponent.Streamer;
import net.sf.openrocket.rocketcomponent.Transition;
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
-import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
-import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightEvent;
-import net.sf.openrocket.simulation.GUISimulationConditions;
import net.sf.openrocket.simulation.FlightEvent.Type;
+import net.sf.openrocket.simulation.GUISimulationConditions;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.LineStyle;
import net.sf.openrocket.util.Reflection;
-
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
+import java.awt.Color;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
/**
* Class that loads a rocket definition from an OpenRocket rocket file.
// BodyTube
setters.put("BodyTube:radius", new DoubleSetter(
- Reflection.findMethodStatic(BodyTube.class, "setRadius", double.class),
+ Reflection.findMethodStatic(BodyTube.class, "setOuterRadius", double.class),
"auto",
Reflection.findMethodStatic(BodyTube.class, "setRadiusAutomatic", boolean.class)));
// LaunchLug
setters.put("LaunchLug:radius", new DoubleSetter(
- Reflection.findMethodStatic(LaunchLug.class, "setRadius", double.class)));
+ Reflection.findMethodStatic(LaunchLug.class, "setOuterRadius", double.class)));
setters.put("LaunchLug:length", new DoubleSetter(
Reflection.findMethodStatic(LaunchLug.class, "setLength", double.class)));
setters.put("LaunchLug:thickness", new DoubleSetter(
if (tube.isRadiusAutomatic())
elements.add("<radius>auto</radius>");
else
- elements.add("<radius>" + tube.getRadius() + "</radius>");
+ elements.add("<radius>" + tube.getOuterRadius() + "</radius>");
if (tube.isMotorMount()) {
elements.addAll(motorMountParams(tube));
package net.sf.openrocket.file.openrocket.savers;
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+
import java.util.ArrayList;
import java.util.List;
-import net.sf.openrocket.rocketcomponent.LaunchLug;
-
public class LaunchLugSaver extends ExternalComponentSaver {
super.addParams(c, elements);
LaunchLug lug = (LaunchLug) c;
- elements.add("<radius>" + lug.getRadius() + "</radius>");
+ elements.add("<radius>" + lug.getOuterRadius() + "</radius>");
elements.add("<length>" + lug.getLength() + "</length>");
elements.add("<thickness>" + lug.getThickness() + "</thickness>");
elements.add("<radialdirection>" + (lug.getRadialDirection()*180.0/Math.PI) + "</radialdirection>");
try {
if ("OD".equals(element)) {
- bodyTube.setRadius(Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_RADIUS);
+ bodyTube.setOuterRadius(Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_RADIUS);
}
if ("ID".equals(element)) {
final double r = Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_RADIUS;
try {
if ("OD".equals(element)) {
- lug.setRadius(Math.max(0, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_RADIUS));
+ lug.setOuterRadius(Math.max(0, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_RADIUS));
}
if ("ID".equals(element)) {
lug.setInnerRadius(Math.max(0, Double.parseDouble(content) / RocksimHandler.ROCKSIM_TO_OPENROCKET_RADIUS));
double packed;
RocketComponent parent = chute.getParent();
if (parent instanceof BodyTube) {
- packed = ((BodyTube) parent).getRadius() * 0.9;
+ packed = ((BodyTube) parent).getOuterRadius() * 0.9;
}
else if (parent instanceof InnerTube) {
packed = ((InnerTube) parent).getInnerRadius() * 0.9;
--- /dev/null
+/*
+ * ColorChooser.java
+ */
+package net.sf.openrocket.gui.components;
+
+import javax.swing.JButton;
+import javax.swing.JColorChooser;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.colorchooser.ColorSelectionModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * A panel implementation of a color chooser. The panel has a label and a textfield. The label identifies
+ * what the color is to be used for (the purpose), and the textfield is uneditable but has its background set
+ * to the currently chosen color as a way of visualizing the color.
+ *
+ * The chosen color may be retrieved via a call to getCurrentColor.
+ */
+public class ColorChooser extends JPanel {
+
+ private static final String COLOR_CHOOSER_BUTTON_LABEL = "Color";
+
+ // The currently selected color
+ private Color curColor;
+
+ final JColorChooser chooser;
+ final JLabel label;
+ final JTextField field;
+ final JPanel p;
+
+ /**
+ * Construct a color chooser as a panel.
+ * l
+ * @param parent the parent panel to which this component will be added
+ * @param theChooser the delegated color chooser; the initial color taken from this chooser
+ * @param theLabel the label used as the 'purpose' of the color; placed next to a textfield
+ */
+ public ColorChooser (JPanel parent, JColorChooser theChooser, final String theLabel) {
+ p = parent;
+ chooser = theChooser;
+ chooser.setPreviewPanel(this);
+ // Initialize the currently selected color
+ curColor = chooser.getColor();
+ label = new JLabel(theLabel + ":");
+
+ parent.add(label, "align right");
+ field = new JTextField();
+ field.setEditable(false);
+ field.setBackground(curColor);
+ parent.add(field, "width 50:100:100");
+
+ final JButton button = new JButton(COLOR_CHOOSER_BUTTON_LABEL);
+
+ ActionListener actionListener = new ActionListener() {
+ public void actionPerformed (ActionEvent actionEvent) {
+ chooser.updateUI();
+
+ final JDialog dialog = JColorChooser.createDialog(null,
+ theLabel, true,
+ chooser,
+ null, null);
+
+ // Wait until current event dispatching completes before showing
+ // dialog
+ Runnable showDialog = new Runnable() {
+ public void run () {
+ dialog.show();
+ }
+ };
+ SwingUtilities.invokeLater(showDialog);
+ }
+ };
+ button.addActionListener(actionListener);
+ parent.add(button, "wrap");
+
+ // Add listener on model to detect changes to selected color
+ ColorSelectionModel model = chooser.getSelectionModel();
+ model.addChangeListener(new ChangeListener() {
+ public void stateChanged (ChangeEvent evt) {
+ ColorSelectionModel model = (ColorSelectionModel) evt.getSource();
+ // Get the new color value
+ curColor = model.getSelectedColor();
+ field.setBackground(curColor);
+ }
+ });
+ }
+
+ /**
+ * Get the user-selected color.
+ *
+ * @return the current color
+ */
+ public Color getCurrentColor () {
+ return curColor;
+ }
+}
\ No newline at end of file
package net.sf.openrocket.gui.configdialog;
-import javax.swing.JCheckBox;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JSpinner;
-
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.BooleanModel;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.unit.UnitGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+
public class BodyTubeConfig extends RocketComponentConfig {
private MotorConfig motorConfigPane = null;
//// Body tube diameter
panel.add(new JLabel("Outer diameter:"));
- DoubleModel od = new DoubleModel(component,"Radius",2,UnitGroup.UNITS_LENGTH,0);
+ DoubleModel od = new DoubleModel(component,"OuterRadius",2,UnitGroup.UNITS_LENGTH,0);
// Diameter = 2*Radius
spin = new JSpinner(od.getSpinnerModel());
package net.sf.openrocket.gui.configdialog;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JSpinner;
-
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.SpinnerEditor;
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.unit.UnitGroup;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+
public class LaunchLugConfig extends RocketComponentConfig {
private MotorConfig motorConfigPane = null;
//// Body tube diameter
panel.add(new JLabel("Outer diameter:"));
- DoubleModel od = new DoubleModel(component,"Radius",2,UnitGroup.UNITS_LENGTH,0);
+ DoubleModel od = new DoubleModel(component,"OuterRadius",2,UnitGroup.UNITS_LENGTH,0);
// Diameter = 2*Radius
spin = new JSpinner(od.getSpinnerModel());
--- /dev/null
+/*
+ * PrintDialog.java
+ */
+package net.sf.openrocket.gui.dialogs;
+
+import net.sf.openrocket.document.OpenRocketDocument;
+import net.sf.openrocket.gui.print.PDFPrintStreamDoc;
+import net.sf.openrocket.gui.print.PrintUtilities;
+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;
+
+/**
+ * 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.
+ */
+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;
+
+ /**
+ * 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);
+ }
+
+ dialog.setVisible(true);
+
+ if (dialog.getStatus() == ServiceDialog.APPROVE) {
+ PrintRequestAttributeSet newas = dialog.getAttributes();
+ Class dstCategory = Destination.class;
+ Class fdCategory = Fidelity.class;
+
+ if (attributes.containsKey(dstCategory) &&
+ !newas.containsKey(dstCategory)) {
+ attributes.remove(dstCategory);
+ }
+
+ attributes.addAll(newas);
+
+ 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;
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * 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);
+
+ if (asUnsupported != null) {
+ Attribute[] usAttrs = asUnsupported.toArray();
+
+ for (Attribute usAttr : usAttrs) {
+ Class<? extends Attribute> category = usAttr.getCategory();
+
+ if (ps.isAttributeCategorySupported(category)) {
+ Attribute attr =
+ (Attribute) ps.getDefaultAttributeValue(category);
+
+ if (attr != null) {
+ aset.add(attr);
+ }
+ else {
+ aset.remove(category);
+ }
+ }
+ else {
+ aset.remove(category);
+ }
+ }
+ }
+ }
+
+}
--- /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.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 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");
+
+ saveAsPDF = new JButton("Save as PDF");
+ saveAsPDF.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed (ActionEvent e) {
+ onSavePDF(PrintPanel.this);
+ }
+ });
+ add(saveAsPDF, "span 2, tag save");
+
+ previewButton = new JButton("Preview");
+ previewButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed (ActionEvent e) {
+ onPreview();
+ }
+ });
+ add(previewButton, "x 150");
+
+ JButton settingsButton = new JButton("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;
+
+ /**
+ * 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");
+
+ JButton closeButton = new JButton("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);
+ }
+
+}
package net.sf.openrocket.gui.figureelements;
-import static net.sf.openrocket.util.Chars.*;
+import net.sf.openrocket.aerodynamics.Warning;
+import net.sf.openrocket.aerodynamics.WarningSet;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Prefs;
import java.awt.Color;
import java.awt.Font;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
-import net.sf.openrocket.aerodynamics.Warning;
-import net.sf.openrocket.aerodynamics.WarningSet;
-import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.simulation.FlightData;
-import net.sf.openrocket.unit.UnitGroup;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Prefs;
+import static net.sf.openrocket.util.Chars.ALPHA;
+import static net.sf.openrocket.util.Chars.THETA;
/**
}
GlyphVector cgValue = createText(
- UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(cg));
+ getCg());
GlyphVector cpValue = createText(
- UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(cp));
+ getCp());
GlyphVector stabValue = createText(
- stabilityUnits.getDefaultUnit().toStringUnit(cp-cg));
+ getStability());
GlyphVector cgText = createText("CG: ");
GlyphVector cpText = createText("CP: ");
}
-
- private void drawWarnings() {
+ /**
+ * Get the mass, in default mass units.
+ *
+ * @return the mass
+ */
+ public double getMass() {
+ return mass;
+ }
+
+ /**
+ * Get the mass in specified mass units.
+ *
+ * @param u UnitGroup.MASS
+ *
+ * @return the mass
+ */
+ public String getMass(Unit u) {
+ return u.toStringUnit(mass);
+ }
+
+ /**
+ * Get the stability, in calibers.
+ *
+ * @return the current stability margin
+ */
+ public String getStability () {
+ return stabilityUnits.getDefaultUnit().toStringUnit(cp-cg);
+ }
+
+ /**
+ * Get the center of pressure in default length units.
+ *
+ * @return the distance from the tip to the center of pressure, in default length units
+ */
+ public String getCp () {
+ return getCp(UnitGroup.UNITS_LENGTH.getDefaultUnit());
+ }
+
+ /**
+ * Get the center of pressure in default length units.
+ *
+ * @param u UnitGroup.LENGTH
+ *
+ * @return the distance from the tip to the center of pressure, in default length units
+ */
+ public String getCp (Unit u) {
+ return u.toStringUnit(cp);
+ }
+
+ /**
+ * Get the center of gravity in default length units.
+ *
+ * @return the distance from the tip to the center of gravity, in default length units
+ */
+ public String getCg () {
+ return getCg(UnitGroup.UNITS_LENGTH.getDefaultUnit());
+ }
+
+ /**
+ * Get the center of gravity in specified length units.
+ *
+ * @param u UnitGroup.LENGTH
+ * @return the distance from the tip to the center of gravity, in specified units
+ */
+ public String getCg (Unit u) {
+ return u.toStringUnit(cg);
+ }
+
+ /**
+ * Get the flight data for the current motor configuration.
+ *
+ * @return flight data, or null
+ */
+ public FlightData getFlightData () {
+ return flightData;
+ }
+
+ private void drawWarnings() {
if (warnings == null || warnings.isEmpty())
return;
package net.sf.openrocket.gui.main;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.Toolkit;
-import java.awt.Window;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLDecoder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-import javax.swing.Action;
-import javax.swing.InputMap;
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JMenu;
-import javax.swing.JMenuBar;
-import javax.swing.JMenuItem;
-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;
-import javax.swing.KeyStroke;
-import javax.swing.ListSelectionModel;
-import javax.swing.ScrollPaneConstants;
-import javax.swing.SwingUtilities;
-import javax.swing.border.TitledBorder;
-import javax.swing.event.TreeSelectionEvent;
-import javax.swing.event.TreeSelectionListener;
-import javax.swing.filechooser.FileFilter;
-import javax.swing.tree.DefaultTreeSelectionModel;
-import javax.swing.tree.TreePath;
-import javax.swing.tree.TreeSelectionModel;
-
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.gui.dialogs.ExampleDesignDialog;
import net.sf.openrocket.gui.dialogs.LicenseDialog;
import net.sf.openrocket.gui.dialogs.MotorDatabaseLoadingDialog;
+import net.sf.openrocket.gui.dialogs.PrintDialog;
import net.sf.openrocket.gui.dialogs.SwingWorkerDialog;
import net.sf.openrocket.gui.dialogs.WarningDialog;
import net.sf.openrocket.gui.dialogs.preferences.PreferencesDialog;
import net.sf.openrocket.util.SaveFileWorker;
import net.sf.openrocket.util.TestRockets;
+import javax.swing.Action;
+import javax.swing.InputMap;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+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;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.filechooser.FileFilter;
+import javax.swing.tree.DefaultTreeSelectionModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
public class BasicFrame extends JFrame {
private static final LogHelper log = Application.getLogger();
menu.add(item);
menu.addSeparator();
-
+
+
+ item = new JMenuItem("Print...", KeyEvent.VK_P);
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
+ item.getAccessibleContext().setAccessibleDescription("Print parts list and fin template");
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ printAction();
+ }
+ });
+ menu.add(item);
+
item = new JMenuItem("Quit", KeyEvent.VK_Q);
item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.CTRL_MASK));
item.getAccessibleContext().setAccessibleDescription("Quit the program");
+ /**
+ *
+ */
+ public void printAction() {
+ new PrintDialog(document);
+ }
+
/**
* Open a new design window with a basic rocket+stage.
*/
--- /dev/null
+/*
+ * DesignReport.java
+ */
+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.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 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.motor.Motor;
+import net.sf.openrocket.rocketcomponent.ComponentVisitor;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.Prefs;
+
+import java.awt.Graphics2D;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.List;
+
+/**
+ * <pre>
+ * # Title # Section describing the rocket in general without motors
+ * # Section describing the rocket in general without motors
+ * <p/>
+ * design name
+ * empty mass & CG
+ * CP position
+ * CP position at 5 degree AOA (or similar)
+ * number of stages
+ * parachute/streamer sizes
+ * max. diameter (caliber)
+ * velocity at exit of rail/rod
+ * minimum safe velocity reached in x inches/cm
+ * <p/>
+ * # Section for each motor configuration
+ * <p/>
+ * a summary of the motors, e.g. 3xC6-0; B4-6
+ * a list of the motors including the manufacturer, designation (maybe also info like burn time, grams of propellant,
+ * total impulse)
+ * total grams of propellant
+ * total impulse
+ * takeoff weight
+ * CG and CP position, stability margin
+ * predicted flight altitude, max. velocity and max. acceleration
+ * predicted velocity at chute deployment
+ * predicted descent rate
+ * Thrust to Weight Ratio of each stage
+ * <p/>
+ * </pre>
+ */
+public class DesignReport extends BaseVisitorStrategy {
+
+ /**
+ * The OR Document.
+ */
+ private OpenRocketDocument rocketDocument;
+
+ /**
+ * A panel used for rendering of the design diagram.
+ */
+ final RocketPanel panel;
+
+ /**
+ * A stage visitor.
+ */
+ private StageVisitorStrategy svs = new StageVisitorStrategy();
+
+ /**
+ * Constructor.
+ *
+ * @param theRocDoc the OR document
+ * @param theIDoc the iText document
+ */
+ public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
+ super(theIDoc, null);
+ rocketDocument = theRocDoc;
+ panel = new RocketPanel(rocketDocument);
+ }
+
+ /**
+ * Main entry point. Prints the rocket drawing and design data.
+ *
+ * @param writer a direct byte writer
+ */
+ public void print (PdfWriter writer) {
+ if (writer == null) {
+ return;
+ }
+ com.itextpdf.text.Rectangle pageSize = document.getPageSize();
+ int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
+ int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
+
+ PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, "Rocket Design");
+
+ Rocket rocket = rocketDocument.getRocket();
+ final Configuration configuration = rocket.getDefaultConfiguration();
+ configuration.setAllStages();
+ PdfContentByte canvas = writer.getDirectContent();
+
+ final PrintFigure figure = new PrintFigure(configuration);
+
+ FigureElement cp = panel.getExtraCP();
+ FigureElement cg = panel.getExtraCG();
+ RocketInfo text = panel.getExtraText();
+
+ double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
+
+ canvas.beginText();
+ try {
+ canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
+ BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
+ }
+ catch (DocumentException e) {
+ e.printStackTrace();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
+ .toPoints(1)));
+ final int diagramHeight = pageImageableHeight * 2 - 70 - (int) (figHeightPts);
+ canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
+ canvas.moveTextWithLeading(0, -16);
+
+ float initialY = canvas.getYTLM();
+
+ canvas.showText(rocketDocument.getRocket().getName());
+
+ canvas.newlineShowText("Stages: ");
+ canvas.showText("" + rocket.getStageCount());
+
+
+ if (configuration.hasMotors()) {
+ canvas.newlineShowText("Mass (with motor" + ((configuration.getStageCount() > 1) ? "s): " : "): "));
+ }
+ else {
+ canvas.newlineShowText("Mass (Empty): ");
+ }
+ canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
+
+ canvas.newlineShowText("Stability: ");
+ canvas.showText(text.getStability());
+
+ canvas.newlineShowText("Cg: ");
+ canvas.showText(text.getCg());
+
+ canvas.newlineShowText("Cp: ");
+ canvas.showText(text.getCp());
+ canvas.endText();
+
+ try {
+ //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
+ Paragraph paragraph = new Paragraph();
+ float finalY = canvas.getYTLM();
+ int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
+
+ paragraph.setSpacingAfter(heightOfDiagramAndText);
+ document.add(paragraph);
+
+ String[] mids = rocket.getMotorConfigurationIDs();
+
+ List<Double> stages = getStageWeights(rocket);
+
+
+ for (int j = 0; j < mids.length; j++) {
+ String mid = mids[j];
+ if (mid != null) {
+ MotorMountVisitorStrategy mmvs = new MotorMountVisitorStrategy(document, mid);
+ rocket.accept(new ComponentVisitor(mmvs));
+ PdfPTable parent = new PdfPTable(2);
+ parent.setWidthPercentage(100);
+ parent.setHorizontalAlignment(Element.ALIGN_LEFT);
+ parent.setSpacingBefore(0);
+ parent.setWidths(new int[]{1, 3});
+ int leading = 0;
+ //The first motor config is always null. Skip it and the top-most motor, then set the leading.
+ if (j > 1) {
+ leading = 25;
+ }
+ addFlightData(rocket, mid, parent, leading);
+ addMotorData(mmvs.getMotors(), parent, stages);
+ document.add(parent);
+ }
+ }
+ }
+ catch (DocumentException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Paint a diagram of the rocket into the PDF document.
+ *
+ * @param thePageImageableWidth the number of points in the width of the page available for drawing
+ * @param thePageImageableHeight the number of points in the height of the page available for drawing
+ * @param theCanvas the direct byte writer
+ * @param theFigure the print figure
+ * @param theCp the center of pressure figure element
+ * @param theCg the center of gravity figure element
+ *
+ * @return the scale of the diagram
+ */
+ private double paintRocketDiagram (final int thePageImageableWidth, final int thePageImageableHeight,
+ final PdfContentByte theCanvas, final PrintFigure theFigure,
+ final FigureElement theCp, final FigureElement theCg) {
+ theFigure.clearAbsoluteExtra();
+ theFigure.clearRelativeExtra();
+ theFigure.addRelativeExtra(theCp);
+ theFigure.addRelativeExtra(theCg);
+ theFigure.updateFigure();
+
+ double scale =
+ (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
+
+ theFigure.setScale(scale);
+ /*
+ * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
+ */
+ theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
+ theFigure.updateFigure();
+
+
+ final DefaultFontMapper mapper = new DefaultFontMapper();
+ Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
+ g2d.translate(20, 120);
+
+ g2d.scale(0.4d, 0.4d);
+ theFigure.paint(g2d);
+ g2d.dispose();
+ return scale;
+ }
+
+ /**
+ * Add the motor data for a motor configuration to the table.
+ *
+ * @param motors a motor configuration's list of motors
+ * @param parent the parent to which the motor data will be added
+ * @param stageWeights the stageWeights of each stage, in order
+ */
+ private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> stageWeights) {
+
+ PdfPTable motorTable = new PdfPTable(8);
+ motorTable.setWidthPercentage(68);
+ motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
+
+ final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM);
+ final int mPad = 10;
+ motorCell.setPaddingLeft(mPad);
+ motorTable.addCell(motorCell);
+ motorTable.addCell(ITextHelper.createCell("Avg Thrust", PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell("Burn Time", PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell("Max Thrust", PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell("Total Impulse", PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell("Thrust to Wt", PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell("Propellant Wt", PdfPCell.BOTTOM));
+ motorTable.addCell(ITextHelper.createCell("Size", PdfPCell.BOTTOM));
+
+ DecimalFormat df = new DecimalFormat("#,##0.0#");
+ for (int i = 0; i < motors.size(); i++) {
+ int border = Rectangle.BOTTOM;
+ if (i == motors.size() - 1) {
+ border = Rectangle.NO_BORDER;
+ }
+ Motor motor = motors.get(i);
+ double motorWeight = (motor.getLaunchCG().weight - motor.getEmptyCG().weight) * 1000; //convert to grams
+
+ final PdfPCell motorVCell = ITextHelper.createCell(motor.getDesignation(), border);
+ motorVCell.setPaddingLeft(mPad);
+ motorTable.addCell(motorVCell);
+ motorTable.addCell(ITextHelper.createCell(df.format(motor.getAverageThrustEstimate()) + " " + UnitGroup
+ .UNITS_FORCE
+ .getDefaultUnit().toString(), border));
+ motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
+ .UNITS_FLIGHT_TIME
+ .getDefaultUnit().toString(), border));
+ motorTable.addCell(ITextHelper.createCell(df.format(motor.getMaxThrustEstimate()) + " " + UnitGroup
+ .UNITS_FORCE.getDefaultUnit()
+ .toString(), border));
+ motorTable.addCell(ITextHelper.createCell(df.format(motor.getTotalImpulseEstimate()) + " " + UnitGroup
+ .UNITS_IMPULSE
+ .getDefaultUnit().toString(), border));
+ double ttw = motor.getAverageThrustEstimate() / (getStageWeight(stageWeights, i) + (motor
+ .getLaunchCG().weight * 9.80665));
+ motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
+
+ motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
+ .getDefaultUnit().toString(), border));
+
+ final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
+ .getDefaultUnit();
+ motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
+ "/" +
+ motorUnit.toString(motor.getLength()) + " " +
+ motorUnit.toString(), border));
+ }
+ PdfPCell c = new PdfPCell(motorTable);
+ c.setBorder(PdfPCell.LEFT);
+ c.setBorderWidthTop(0f);
+ parent.addCell(c);
+ }
+
+
+ /**
+ * Add the motor data for a motor configuration to the table.
+ *
+ * @param theRocket the rocket
+ * @param mid a motor configuration id
+ * @param parent the parent to which the motor data will be added
+ * @param leading the number of points for the leading
+ */
+ private void addFlightData (final Rocket theRocket, final String mid, final PdfPTable parent, int leading) {
+ FlightData flight = null;
+ if (theRocket.getMotorConfigurationIDs().length > 1) {
+ Rocket duplicate= theRocket.copyWithOriginalID();
+ Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
+ simulation.getConditions().setMotorConfigurationID(mid);
+
+ flight = PrintSimulationWorker.doit(simulation);
+
+ if (flight != null) {
+ try {
+ final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
+ final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
+ final Unit flightUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
+
+ PdfPTable labelTable = new PdfPTable(2);
+ labelTable.setWidths(new int[]{3, 2});
+ final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
+ theRocket.getMotorConfigurationNameOrDescription(mid)), PrintUtilities.BOLD);
+ chunk.setLeading(leading);
+ chunk.setSpacingAfter(3f);
+
+ document.add(chunk);
+
+ DecimalFormat df = new DecimalFormat("#,##0.0#");
+
+ final PdfPCell cell = ITextHelper.createCell("Altitude", 2, 2);
+ cell.setUseBorderPadding(false);
+ cell.setBorderWidthTop(0f);
+ labelTable.addCell(cell);
+ labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxAltitude()) + " " + distanceUnit,
+ 2, 2));
+
+ labelTable.addCell(ITextHelper.createCell("Flight Time", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
+ 2));
+
+ labelTable.addCell(ITextHelper.createCell("Time to Apogee", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
+ 2));
+
+ labelTable.addCell(ITextHelper.createCell("Velocity off Pad", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(df.format(
+ flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
+
+ labelTable.addCell(ITextHelper.createCell("Max Velocity", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
+ 2, 2));
+
+ labelTable.addCell(ITextHelper.createCell("Landing Velocity", 2, 2));
+ labelTable.addCell(ITextHelper.createCell(df.format(
+ flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
+
+ //Add the table to the parent; have to wrap it in a cell
+ PdfPCell c = new PdfPCell(labelTable);
+ c.setBorder(PdfPCell.RIGHT);
+ c.setBorderWidthTop(0);
+ c.setTop(0);
+ parent.addCell(c);
+ }
+ catch (DocumentException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Strip [] brackets from a string.
+ *
+ * @param target the original string
+ *
+ * @return target with [] removed
+ */
+ private String stripBrackets (String target) {
+ return stripLeftBracket(stripRightBracket(target));
+ }
+
+ /**
+ * Strip [ from a string.
+ *
+ * @param target the original string
+ *
+ * @return target with [ removed
+ */
+ private String stripLeftBracket (String target) {
+ return target.replace("[", "");
+ }
+
+ /**
+ * Strip ] from a string.
+ *
+ * @param target the original string
+ *
+ * @return target with ] removed
+ */
+ private String stripRightBracket (String target) {
+ return target.replace("]", "");
+ }
+
+ /**
+ * Use a visitor to get the sorted list of Stage references, then from those 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();
+ for (int i = 0; i < stages.size(); i++) {
+ Double stage = stages.get(i);
+ stages.set(i, stage * 9.80665);
+ }
+ return stages;
+ }
+
+ /**
+ * Compute the total stage weight from a list of stage weights. This sums up the weight of the given stage plus all
+ * stages that sit atop it (depend upon it for thrust).
+ *
+ * @param weights the list of stage weights, in Newtons
+ * @param stage a stage number, 0 being topmost stage
+ *
+ * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
+ */
+ private double getStageWeight (List<Double> weights, int stage) {
+
+ double result = 0d;
+ for (int i = 0; i <= stage; i++) {
+ result += weights.get(i);
+ }
+ return result;
+ }
+}
--- /dev/null
+/*
+ * ITextHelper.java
+ */
+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 com.itextpdf.text.Phrase;
+import com.itextpdf.text.Rectangle;
+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 java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * A bunch of helper methods for creating iText components.
+ */
+public final class ITextHelper {
+
+ /**
+ * Create a cell for an iText table.
+ *
+ * @return a cell with bottom border
+ */
+ public static PdfPCell createCell () {
+ return createCell(Rectangle.BOTTOM);
+ }
+
+ /**
+ * Create a cell for an iText table with the given border location.
+ *
+ * @param border the border location
+ *
+ * @return a cell with given border
+ */
+ public static PdfPCell createCell (int border) {
+ PdfPCell result = new PdfPCell();
+ result.setBorder(border);
+
+ return result;
+ }
+
+ /**
+ * Create a cell whose contents are a table. No border.
+ *
+ * @param table the table to insert into the cell
+ *
+ * @return the cell containing a table
+ */
+ public static PdfPCell createCell (PdfPTable table) {
+ PdfPCell result = new PdfPCell();
+ result.setBorder(PdfPCell.NO_BORDER);
+ result.addElement(table);
+
+ return result;
+ }
+
+ /**
+ * Create a cell whose contents are the given string. No border. Standard PrintUtilities.NORMAL font.
+ *
+ * @param v the text of the cell.
+ *
+ * @return the cell containing the text
+ */
+ public static PdfPCell createCell (String v) {
+ return createCell(v, Rectangle.NO_BORDER, PrintUtilities.NORMAL);
+ }
+
+ /**
+ * Create a cell whose contents are the given string , rendered with the given font. No border.
+ *
+ * @param v the text of the cell
+ * @param font the font
+ *
+ * @return the cell containing the text
+ */
+ public static PdfPCell createCell (String v, Font font) {
+ return createCell(v, Rectangle.NO_BORDER, font);
+ }
+
+ /**
+ * Create a cell whose contents are the given string with specified left and right padding (spacing).
+ *
+ * @param v the text of the cell
+ * @param leftPad the number of points to precede the text
+ * @param rightPad the number of points to follow the text
+ *
+ * @return the cell containing the text
+ */
+ public static PdfPCell createCell (String v, int leftPad, int rightPad) {
+ PdfPCell c = createCell(v, Rectangle.NO_BORDER, PrintUtilities.NORMAL);
+ c.setPaddingLeft(leftPad);
+ c.setPaddingRight(rightPad);
+ return c;
+ }
+
+ /**
+ * Create a cell whose contents are the given string with the given border. Uses NORMAL font.
+ *
+ * @param v the text of the cell
+ * @param border the border type
+ *
+ * @return the cell containing the text
+ */
+ public static PdfPCell createCell (String v, int border) {
+ return createCell(v, border, PrintUtilities.NORMAL);
+ }
+
+ /**
+ * Complete create cell - fully qualified. Create a cell whose contents are the given string with the given border
+ * and font.
+ *
+ * @param v the text of the cell
+ * @param border the border type
+ * @param font the font
+ *
+ * @return the cell containing the text
+ */
+ public static PdfPCell createCell (String v, int border, Font font) {
+ PdfPCell result = new PdfPCell();
+ result.setBorder(border);
+ Chunk c = new Chunk();
+ c.setFont(font);
+ c.append(v);
+ result.addElement(c);
+ return result;
+ }
+
+ /**
+ * Create a phrase with the given text and font.
+ *
+ * @param text the text
+ * @param font the font
+ *
+ * @return an iText phrase
+ */
+ public static Phrase createPhrase (String text, Font font) {
+ Phrase p = new Phrase();
+ final Chunk chunk = new Chunk(text);
+ chunk.setFont(font);
+ p.add(chunk);
+ return p;
+ }
+
+ /**
+ * Create a phrase with the given text.
+ *
+ * @param text the text
+ *
+ * @return an iText phrase
+ */
+ public static Phrase createPhrase (String text) {
+ return createPhrase(text, PrintUtilities.NORMAL);
+ }
+
+ /**
+ * Create a paragraph with the given text and font.
+ *
+ * @param text the text
+ * @param font the font
+ *
+ * @return an iText paragraph
+ */
+ public static Paragraph createParagraph (String text, Font font) {
+ Paragraph p = new Paragraph();
+ final Chunk chunk = new Chunk(text);
+ chunk.setFont(font);
+ p.add(chunk);
+ return p;
+ }
+
+ /**
+ * Create a paragraph with the given text and using NORMAL font.
+ *
+ * @param text the text
+ *
+ * @return an iText paragraph
+ */
+ public static Paragraph createParagraph (String text) {
+ return createParagraph(text, PrintUtilities.NORMAL);
+ }
+
+ /**
+ * Break a large image up into page-size pieces and output each page in order to an iText document. The image is
+ * overlayed with an matrix of pages running from left to right until the right side of the image is reached. Then
+ * the next 'row' of pages is output from left to right, and so on.
+ *
+ * @param pageSize a rectangle that defines the bounds of the page size
+ * @param doc the iText document
+ * @param writer the underlying content writer
+ * @param image the source image
+ *
+ * @throws DocumentException thrown if the document could not be written
+ */
+ public static void renderImageAcrossPages (Rectangle pageSize, Document doc, PdfWriter writer, java.awt.Image image)
+ throws DocumentException {
+ // 4/10 of an inch margin
+ final int margin = (int) (PrintUnit.POINTS_PER_INCH * 0.4f);
+ float wPage = pageSize.getWidth() - 2 * margin;
+ float hPage = pageSize.getHeight() - 2 * margin;
+
+ float wImage = image.getWidth(null);
+ float hImage = image.getHeight(null);
+
+ java.awt.Rectangle crop = new java.awt.Rectangle(0, 0, (int) Math.min(wPage, wImage), (int) Math.min(hPage,
+ hImage));
+ PdfContentByte content = writer.getDirectContent();
+
+ double adjust;
+ while (true) {
+ BufferedImage subImage = ((BufferedImage) image).getSubimage((int) crop.getX(), (int) crop.getY(),
+ (int) crop.getWidth(), (int) crop.getHeight());
+
+ Graphics2D g2 = content.createGraphics(wPage, hPage);
+ g2.drawImage(subImage, margin - 1, margin - 1, null);
+ g2.dispose();
+ doc.newPage();
+ final int newX = (int) (crop.getWidth() + crop.getX());
+ if (newX < wImage) {
+ adjust = Math.min(wImage - newX, wPage);
+ crop = new java.awt.Rectangle(newX, (int) crop.getY(), (int) adjust,
+ (int) crop.getHeight());
+ }
+ else {
+ final int newY = (int) (crop.getHeight() + crop.getY());
+ if (newY < hImage) {
+ adjust = Math.min(hImage - newY, hPage);
+ crop = new java.awt.Rectangle(0, newY, (int) Math.min(wPage, wImage), (int) adjust);
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * OpenRocketPrintable.java
+ */
+package net.sf.openrocket.gui.print;
+
+/**
+ * This enumeration identifies the various types of information that may be printed.
+ */
+public enum OpenRocketPrintable {
+ //PARTS_LIST("Parts list", true, 0),
+ PARTS_DETAIL("Parts detail", true, 1),
+ FIN_TEMPLATE("Fin templates", true, 2),
+ DESIGN_REPORT("Design Report", false, 3);
+
+ /**
+ * The description - will be displayed in the JTree.
+ */
+ private String description;
+
+ /**
+ * Flag that indicates if the enum value is different depending upon stage.
+ */
+ private boolean stageSpecific;
+
+ /**
+ * The order of the item as it appears in the printed document.
+ */
+ private int order;
+
+ /**
+ * Constructor.
+ *
+ * @param s the displayable description
+ * @param staged indicates if the printable is stage dependent
+ * @param idx the relative print order
+ */
+ OpenRocketPrintable (String s, boolean staged, int idx) {
+ description = s;
+ stageSpecific = staged;
+ order = idx;
+ }
+
+ /**
+ * Get the description of this printable.
+ *
+ * @return a displayable string
+ */
+ public String getDescription () {
+ return description;
+ }
+
+ /**
+ * Answers if this enum value has different meaning depending upon the stage.
+ *
+ * @return true if the printable is stage dependent
+ */
+ public boolean isStageSpecific () {
+ return stageSpecific;
+ }
+
+ /**
+ * Answer the print order. This is relative to other enum values. No two enum values will have the same print
+ * order value.
+ *
+ * @return a 0 based order (0 being first, or highest)
+ */
+ public int getPrintOrder () {
+ return order;
+ }
+
+ /**
+ * Look up an enum value based on the description.
+ *
+ * @param target the description
+ *
+ * @return an instance of this enum class or null if not found
+ */
+ public static OpenRocketPrintable findByDescription (String target) {
+ OpenRocketPrintable[] values = values();
+ for (OpenRocketPrintable value : values) {
+ if (value.getDescription().equalsIgnoreCase(target)) {
+ return value;
+ }
+ }
+ return null;
+ }
+}
--- /dev/null
+/*
+ * PDFPrintStreamDoc.java
+ */
+package net.sf.openrocket.gui.print;
+
+import javax.print.Doc;
+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;
+
+/**
+ */
+public class PDFPrintStreamDoc implements Doc {
+
+ private InputStream stream;
+ private DocAttributeSet attributeSet;
+
+ public PDFPrintStreamDoc (ByteArrayOutputStream ostream, DocAttributeSet attributes) {
+ stream = new ByteArrayInputStream(ostream.toByteArray());
+ if (attributes != null) {
+ attributeSet = AttributeSetUtilities.unmodifiableView(attributes);
+ }
+ }
+
+ public DocFlavor getDocFlavor () {
+ return DocFlavor.INPUT_STREAM.PDF;
+ }
+
+ public DocAttributeSet getAttributes () {
+ return attributeSet;
+ }
+
+ /* Since the data is to be supplied as an InputStream delegate to
+ * getStreamForBytes().
+ */
+
+ public Object getPrintData () throws IOException {
+ return getStreamForBytes();
+ }
+
+ public Reader getReaderForText () {
+ return null;
+ }
+
+ /* Return the print data as an InputStream.
+ * Always return the same instance.
+ */
+
+ public InputStream getStreamForBytes () throws IOException {
+ return stream;
+ }
+}
--- /dev/null
+/*
+ * PaperSize.java
+ */
+package net.sf.openrocket.gui.print;
+
+import com.itextpdf.text.PageSize;
+import com.itextpdf.text.Rectangle;
+import com.itextpdf.text.RectangleReadOnly;
+
+import javax.print.attribute.standard.MediaSizeName;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 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);
+ }
+
+ 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);
+ }
+
+ 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)));
+ }
+
+}
--- /dev/null
+/*
+ * PrintController.java
+ *
+ */
+package net.sf.openrocket.gui.print;
+
+import com.itextpdf.text.Document;
+import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.ExceptionConverter;
+import com.itextpdf.text.Rectangle;
+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;
+ }
+
+}
--- /dev/null
+/*
+ * PrintFigure.java
+ */
+package net.sf.openrocket.gui.print;
+
+import net.sf.openrocket.gui.scalefigure.RocketFigure;
+import net.sf.openrocket.rocketcomponent.Configuration;
+
+/**
+ * A figure used to override the scale factor in RocketFigure. This allows pinpoint scaling to allow a diagram
+ * to fit in the width of the chosen page size.
+ */
+public class PrintFigure extends RocketFigure {
+
+ /**
+ * Constructor.
+ *
+ * @param configuration the configuration
+ */
+ public PrintFigure (final Configuration configuration) {
+ super(configuration);
+ }
+
+ protected double computeTy (int heightPx) {
+ super.computeTy(heightPx);
+ return 0;
+ }
+
+ public void setScale (final double theScale) {
+ this.scale = theScale; //dpi/0.0254*scaling;
+ updateFigure();
+ }
+}
--- /dev/null
+/*
+ * PrintSimulationWorker.java
+ */
+package net.sf.openrocket.gui.print;
+
+import net.sf.openrocket.document.Simulation;
+import net.sf.openrocket.gui.main.SimulationWorker;
+import net.sf.openrocket.simulation.FlightData;
+
+/**
+ * A SimulationWorker that simulates the rocket flight in the background and sets the results to the extra text when
+ * finished. The worker can be cancelled if necessary.
+ */
+public class PrintSimulationWorker {
+
+ public static FlightData doit (Simulation sim) {
+ return new InnerPrintSimulationWorker(sim).doit();
+ }
+
+ static class InnerPrintSimulationWorker extends SimulationWorker {
+
+ public InnerPrintSimulationWorker (Simulation sim) {
+ super(sim);
+ }
+
+ public FlightData doit() {
+ return doInBackground();
+ }
+ @Override
+ protected void simulationDone () {
+ // Do nothing if cancelled
+ if (isCancelled()) {
+ return;
+ }
+
+ simulation.getSimulatedData();
+ }
+
+
+ /**
+ * Called if the simulation is interrupted due to an exception.
+ *
+ * @param t the Throwable that caused the interruption
+ */
+ @Override
+ protected void simulationInterrupted (final Throwable t) {
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * PrintUnit.java
+ */
+package net.sf.openrocket.gui.print;
+
+/**
+ * Utilities for print units.
+ */
+public enum PrintUnit {
+ INCHES {
+ public double toInches(double d) { return d; }
+ public double toMillis(double d) { return d/INCHES_PER_MM; }
+ public double toCentis(double d) { return d/(INCHES_PER_MM*TEN); }
+ public double toMeters(double d) { return d/(INCHES_PER_MM*TEN*TEN*TEN); }
+ public long toPoints(double d) { return (long)(d * POINTS_PER_INCH); }
+ public double convert(double d, PrintUnit u) { return u.toInches(d); }
+ },
+ MILLIMETERS {
+ public double toInches(double d) { return d * INCHES_PER_MM; }
+ public double toMillis(double d) { return d; }
+ public double toCentis(double d) { return d/TEN; }
+ public double toMeters(double d) { return d/(TEN*TEN*TEN); }
+ public long toPoints(double d) { return INCHES.toPoints(toInches(d)); }
+ public double convert(double d, PrintUnit u) { return u.toMillis(d); }
+ },
+ CENTIMETERS {
+ public double toInches(double d) { return d * INCHES_PER_MM * TEN; }
+ public double toMillis(double d) { return d * TEN; }
+ public double toCentis(double d) { return d; }
+ public double toMeters(double d) { return d/(TEN*TEN); }
+ public long toPoints(double d) { return INCHES.toPoints(toInches(d)); }
+ public double convert(double d, PrintUnit u) { return u.toCentis(d); }
+ },
+ METERS {
+ public double toInches(double d) { return d * INCHES_PER_MM * TEN * TEN * TEN; }
+ public double toMillis(double d) { return d * TEN * TEN * TEN; }
+ public double toCentis(double d) { return d * TEN * TEN; }
+ public double toMeters(double d) { return d; }
+ public long toPoints(double d) { return INCHES.toPoints(toInches(d)); }
+ public double convert(double d, PrintUnit u) { return u.toMeters(d); }
+ },
+ POINTS {
+ public double toInches(double d) { return d/POINTS_PER_INCH; }
+ public double toMillis(double d) { return d/(POINTS_PER_INCH * INCHES_PER_MM); }
+ public double toCentis(double d) { return toMillis(d)/TEN; }
+ public double toMeters(double d) { return toMillis(d)/(TEN*TEN*TEN); }
+ public long toPoints(double d) { return (long)d; }
+ public double convert(double d, PrintUnit u) { return u.toPoints(d); }
+ };
+
+ // Handy constants for conversion methods
+ public static final double INCHES_PER_MM = 0.0393700787d;
+ public static final double MM_PER_INCH = 1.0d/INCHES_PER_MM;
+ public static final long TEN = 10;
+ /**
+ * PPI is Postscript Point and is a standard of 72. Java2D also uses this internally as a pixel-per-inch, so pixels
+ * and points are for the most part interchangeable (unless the defaults are changed), which makes translating
+ * between the screen and a print job easier.
+ *
+ * Not to be confused with Dots-Per-Inch, which is printer and print mode dependent.
+ */
+ public static final int POINTS_PER_INCH = 72;
+
+ // To maintain full signature compatibility with 1.5, and to improve the
+ // clarity of the generated javadoc (see 6287639: Abstract methods in
+ // enum classes should not be listed as abstract), method convert
+ // etc. are not declared abstract but otherwise act as abstract methods.
+
+ /**
+ * Convert the given length in the given unit to this
+ * unit. Conversions from finer to coarser granularities
+ * truncate, so may lose precision.
+ *
+ * <p>For example, to convert 10 inches to point, use:
+ * <tt>PrintUnit.POINTS.convert(10L, PrintUnit.INCHES)</tt>
+ *
+ * @param sourceLength the length in the given <tt>sourceUnit</tt>
+ * @param sourceUnit the unit of the <tt>sourceDuration</tt> argument
+ *
+ * @return the converted length in this unit,
+ * or <tt>Long.MIN_VALUE</tt> if conversion would negatively
+ * overflow, or <tt>Long.MAX_VALUE</tt> if it would positively overflow.
+ */
+ public double convert(double sourceLength, PrintUnit sourceUnit) {
+ throw new AbstractMethodError();
+ }
+
+ /**
+ * Equivalent to <tt>INCHES.convert(length, this)</tt>.
+ *
+ * @param length the length
+ *
+ * @return the converted length,
+ * or <tt>Long.MIN_VALUE</tt> if conversion would negatively
+ * overflow, or <tt>Long.MAX_VALUE</tt> if it would positively overflow.
+ * @see #convert
+ */
+ public double toInches(double length) {
+ throw new AbstractMethodError();
+ }
+
+ /**
+ * Equivalent to <tt>MILLIMETERS.convert(length, this)</tt>.
+ *
+ * @param length the length
+ *
+ * @return the converted length,
+ * or <tt>Long.MIN_VALUE</tt> if conversion would negatively
+ * overflow, or <tt>Long.MAX_VALUE</tt> if it would positively overflow.
+ * @see #convert
+ */
+ public double toMillis(double length) {
+ throw new AbstractMethodError();
+ }
+
+ /**
+ * Equivalent to <tt>CENTIMETERS.convert(length, this)</tt>.
+ *
+ * @param length the length
+ *
+ * @return the converted length,
+ * or <tt>Long.MIN_VALUE</tt> if conversion would negatively
+ * overflow, or <tt>Long.MAX_VALUE</tt> if it would positively overflow.
+ * @see #convert
+ */
+ public double toCentis(double length) {
+ throw new AbstractMethodError();
+ }
+
+ /**
+ * Equivalent to <tt>METERS.convert(length, this)</tt>.
+ *
+ * @param length the length
+ *
+ * @return the converted length,
+ * or <tt>Long.MIN_VALUE</tt> if conversion would negatively
+ * overflow, or <tt>Long.MAX_VALUE</tt> if it would positively overflow.
+ * @see #convert
+ */
+ public double toMeters(double length) {
+ throw new AbstractMethodError();
+ }
+
+ /**
+ * Equivalent to <tt>POINTS.convert(length, this)</tt>.
+ *
+ * @param length the length
+ *
+ * @return the converted length,
+ * or <tt>Long.MIN_VALUE</tt> if conversion would negatively
+ * overflow, or <tt>Long.MAX_VALUE</tt> if it would positively overflow.
+ * @see #convert
+ */
+ public long toPoints(double length) {
+ throw new AbstractMethodError();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * PrintUtilities.java
+ */
+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);
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * 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());
+ }
+
+ 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * PrintableContext.java
+ *
+ */
+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;
+
+/**
+ * Instances of this class are meant to keep track of what the user has selected to be printed.
+ */
+public class PrintableContext implements Comparable<PrintableContext>, Iterable<PrintableContext> {
+
+ /**
+ * The stage number. May be null for printables that have no stage meaning.
+ */
+ private Set<Integer> stageNumber;
+
+ /**
+ * The type of thing to be printed.
+ */
+ private OpenRocketPrintable printable;
+
+ private final Map<OpenRocketPrintable, Set<Integer>> previous = new TreeMap<OpenRocketPrintable, Set<Integer>>();
+
+
+ public PrintableContext () {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param theStageNumber the stage number of the printable; may be null if not applicable
+ * @param thePrintable the type of the thing to be printed
+ *
+ * @throws IllegalArgumentException thrown if thePrintable.isStageSpecific
+ */
+ private PrintableContext (final Set<Integer> theStageNumber, final OpenRocketPrintable thePrintable)
+ throws IllegalArgumentException {
+ if (thePrintable.isStageSpecific() && theStageNumber == null) {
+ throw new IllegalArgumentException("A stage number must be provided when a printable is stage specific.");
+ }
+ stageNumber = theStageNumber;
+ printable = thePrintable;
+ }
+
+ public void add (final Integer theStageNumber, final OpenRocketPrintable thePrintable)
+ throws IllegalArgumentException {
+ Set<Integer> stages = previous.get(thePrintable);
+ if (stages == null) {
+ stages = new TreeSet<Integer>();
+ previous.put(thePrintable, stages);
+ }
+ if (theStageNumber != null) {
+ stages.add(theStageNumber);
+ }
+ }
+
+
+ public Iterator<PrintableContext> iterator () {
+ return new Iterator<PrintableContext>() {
+
+ Iterator<OpenRocketPrintable> keyIter = previous.keySet().iterator();
+
+ @Override
+ public boolean hasNext () {
+ return keyIter.hasNext();
+ }
+
+ @Override
+ public PrintableContext next () {
+ final OpenRocketPrintable key = keyIter.next();
+ return new PrintableContext(previous.get(key), key);
+ }
+
+ @Override
+ public void remove () {
+ }
+ };
+
+ }
+
+ /**
+ * Get the stage number, if it's applicable to the printable.
+ *
+ * @return the stage number
+ */
+ public Set<Integer> getStageNumber () {
+ return stageNumber;
+ }
+
+ /**
+ * Get the printable.
+ *
+ * @return the printable
+ */
+ public OpenRocketPrintable getPrintable () {
+ return printable;
+ }
+
+ @Override
+ public int compareTo (final PrintableContext other) {
+ return this.printable.getPrintOrder() - other.printable.getPrintOrder();
+ }
+
+}
--- /dev/null
+/*
+ * PrintableFinSet.java
+ */
+package net.sf.openrocket.gui.print;
+
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.util.Coordinate;
+
+import javax.swing.JPanel;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.geom.GeneralPath;
+import java.awt.image.BufferedImage;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+
+/**
+ * This class allows for a FinSet to be printable. It does so by decorating an existing finset (which will not be
+ * modified) and rendering it within a JPanel. The JPanel is not actually visualized on a display, but instead renders
+ * it to a print device.
+ */
+public class PrintableFinSet extends JPanel implements Printable {
+
+ /**
+ * The object that represents the shape (outline) of the fin. This gets drawn onto the Swing component.
+ */
+ protected GeneralPath polygon = null;
+
+ /**
+ * The X margin.
+ */
+ private int marginX = 25;
+ /**
+ * The Y margin.
+ */
+ private int marginY = 25;
+
+ /**
+ * Constructor.
+ *
+ * @param fs the finset to print
+ */
+ public PrintableFinSet (FinSet fs) {
+ this(fs.getFinPointsWithTab());
+ }
+
+ /**
+ * Construct a fin set from a set of points.
+ *
+ * @param points an array of points.
+ */
+ public PrintableFinSet (Coordinate[] points) {
+ super(false);
+ init(points);
+ setBackground(Color.white);
+ }
+
+ /**
+ * Initialize the fin set polygon and set the size of the component.
+ *
+ * @param points an array of points.
+ */
+ private void init (Coordinate[] points) {
+
+ polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD, points.length);
+ polygon.moveTo(0, 0);
+
+ int minX = 0;
+ int minY = 0;
+ int maxX = 0;
+ int maxY = 0;
+
+ for (Coordinate point : points) {
+ final long x = PrintUnit.METERS.toPoints(point.x);
+ final long y = PrintUnit.METERS.toPoints(point.y);
+ minX = (int) Math.min(x, minX);
+ minY = (int) Math.min(y, minY);
+ maxX = (int) Math.max(x, maxX);
+ maxY = (int) Math.max(y, maxY);
+ polygon.lineTo(x, y);
+ }
+ polygon.closePath();
+
+ if (minX < 0) {
+ marginX += Math.abs(minX);
+ }
+ if (minY < 0) {
+ marginY += Math.abs(minY);
+ }
+ setSize(maxX - minX + marginX, maxY - minY + marginY);
+ }
+
+ /**
+ * Get the X-axis margin value.
+ *
+ * @return margin, in points
+ */
+ protected double getMarginX () {
+ return marginX;
+ }
+
+ /**
+ * Get the Y-axis margin value.
+ *
+ * @return margin, in points
+ */
+ protected double getMarginY () {
+ return marginY;
+ }
+
+ /**
+ * From the java.awt.print.Printable interface.
+ * <p/>
+ * Prints the page at the specified index into the specified {@link java.awt.Graphics} context in the specified
+ * format. A <code>PrinterJob</code> calls the <code>Printable</code> interface to request that a page be rendered
+ * into the context specified by <code>graphics</code>. The format of the page to be drawn is specified by
+ * <code>pageFormat</code>. The zero based index of the requested page is specified by <code>pageIndex</code>. If
+ * the requested page does not exist then this method returns NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned. The
+ * <code>Graphics</code> class or subclass implements the {@link java.awt.print.PrinterGraphics} interface to
+ * provide additional information. If the <code>Printable</code> object aborts the print job then it throws a
+ * {@link java.awt.print.PrinterException}.
+ * <p/>
+ * Note: This is not currently used in OpenRocket. It's only here for reference.
+ *
+ * @param graphics the context into which the page is drawn
+ * @param pageFormat the size and orientation of the page being drawn
+ * @param pageIndex the zero based index of the page to be drawn
+ *
+ * @return PAGE_EXISTS if the page is rendered successfully or NO_SUCH_PAGE if <code>pageIndex</code> specifies a
+ * non-existent page.
+ *
+ * @throws java.awt.print.PrinterException
+ * thrown when the print job is terminated.
+ */
+ @Override
+ public int print (final Graphics graphics, final PageFormat pageFormat, final int pageIndex)
+ throws PrinterException {
+
+ Graphics2D g2d = (Graphics2D) graphics;
+ PrintUtilities.translateToJavaOrigin(g2d, pageFormat);
+ PrintUtilities.disableDoubleBuffering(this);
+ paint(g2d);
+ PrintUtilities.enableDoubleBuffering(this);
+ return Printable.PAGE_EXISTS;
+ }
+
+ /**
+ * Returns a generated image of the fin set. May then be used wherever AWT images can be used, or converted to
+ * another image/picture format and used accordingly.
+ *
+ * @return an awt image of the fin set
+ */
+ public Image createImage () {
+ int width = getWidth() + marginX;
+ int height = getHeight() + marginY;
+ // Create a buffered image in which to draw
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ // Create a graphics contents on the buffered image
+ Graphics2D g2d = bufferedImage.createGraphics();
+ // Draw graphics
+ g2d.setBackground(Color.white);
+ g2d.clearRect(0, 0, width, height);
+ paintComponent(g2d);
+ // Graphics context no longer needed so dispose it
+ g2d.dispose();
+ return bufferedImage;
+ }
+
+ /**
+ * Render the fin set onto the graphics context. This is done by creating a GeneralPath component that follows the
+ * outline of the fin set coordinates to create a polygon, which is then drawn onto the graphics context.
+ * Through-the-wall fin tabs are supported if they are present.
+ *
+ * @param g the Java2D graphics context
+ */
+ @Override
+ public void paintComponent (Graphics g) {
+ super.paintComponent(g);
+ Graphics2D g2d = (Graphics2D) g;
+
+ g2d.translate(marginX, marginY);
+ g2d.setPaint(TemplateProperties.getFillColor());
+ g2d.fill(polygon);
+ g2d.setPaint(TemplateProperties.getLineColor());
+ g2d.draw(polygon);
+ }
+
+}
--- /dev/null
+/*
+ * TemplateProperties.java
+ */
+package net.sf.openrocket.gui.print;
+
+import javax.swing.UIManager;
+import java.awt.Color;
+
+/**
+ * This class is responsible for managing various properties of print templates (fin, nose cone, transitions, etc.).
+ */
+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;
+ }
+}
--- /dev/null
+/*
+ * CheckBoxNode.java
+ */
+package net.sf.openrocket.gui.print.components;
+
+/**
+ * A class that acts as the textual node of the check box within the JTree.
+ */
+public class CheckBoxNode {
+
+ /**
+ * The text label of the check box.
+ */
+ String text;
+
+ /**
+ * State flag indicating if the check box has been selected.
+ */
+ boolean selected;
+
+ /**
+ * Constructor.
+ *
+ * @param theText the check box label
+ * @param isSelected true if selected
+ */
+ public CheckBoxNode (String theText, boolean isSelected) {
+ text = theText;
+ selected = isSelected;
+ }
+
+ /**
+ * Get the current state of the check box.
+ *
+ * @return true if selected
+ */
+ public boolean isSelected () {
+ return selected;
+ }
+
+ /**
+ * Set the current state of the check box. Note: this just tracks the state - it
+ * does NOT actually set the state of the check box.
+ *
+ * @param isSelected true if selected
+ */
+ public void setSelected (boolean isSelected) {
+ selected = isSelected;
+ }
+
+ /**
+ * Get the text of the label.
+ *
+ * @return the text of the label
+ */
+ public String getText () {
+ return text;
+ }
+
+ /**
+ * Set the text of the label of the check box.
+ *
+ * @param theText the text of the label
+ */
+ public void setText (String theText) {
+ text = theText;
+ }
+
+ /**
+ * If someone prints this object, the text label will be displayed.
+ *
+ * @return the text label
+ */
+ public String toString () {
+ return text;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * CheckTreeCellRenderer.java
+ */
+package net.sf.openrocket.gui.print.components;
+
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JTree;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreePath;
+import java.awt.BorderLayout;
+import java.awt.Component;
+
+/**
+ * A cell renderer for JCheckBoxes within nodes of a JTree.
+ * <p/>
+ * Based in part on a blog by Santhosh Kumar. http://www.jroller.com/santhosh/date/20050610
+ */
+public class CheckTreeCellRenderer extends JPanel implements TreeCellRenderer {
+
+ /**
+ * The selection model.
+ */
+ private CheckTreeSelectionModel selectionModel;
+ /**
+ * The delegated cell renderer.
+ */
+ private DefaultTreeCellRenderer delegate;
+ /**
+ * The check box within this cell.
+ */
+ private JCheckBox checkBox = new JCheckBox();
+
+ /**
+ * Constructor.
+ *
+ * @param theDelegate the delegated cell renderer
+ * @param theSelectionModel the selection model
+ */
+ public CheckTreeCellRenderer (DefaultTreeCellRenderer theDelegate, CheckTreeSelectionModel theSelectionModel) {
+ delegate = theDelegate;
+
+ delegate.setLeafIcon(null);
+ delegate.setClosedIcon(null);
+ delegate.setOpenIcon(null);
+
+
+ selectionModel = theSelectionModel;
+ setLayout(new BorderLayout());
+ setOpaque(false);
+ checkBox.setOpaque(false);
+ checkBox.setSelected(true);
+ }
+
+ /**
+ * @{inheritDoc}
+ */
+ @Override
+ public Component getTreeCellRendererComponent (JTree tree, Object value, boolean selected, boolean expanded,
+ boolean leaf, int row, boolean hasFocus) {
+ Component renderer = delegate.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,
+ hasFocus);
+
+ TreePath path = tree.getPathForRow(row);
+ if (path != null) {
+ final boolean b = selectionModel.isPathSelected(path, true);
+ checkBox.setSelected(b);
+ if (value instanceof DefaultMutableTreeNode) {
+ Object obj = ((DefaultMutableTreeNode) value).getUserObject();
+ if (obj instanceof CheckBoxNode) {
+ ((CheckBoxNode) obj).setSelected(b);
+ }
+ }
+ }
+
+ removeAll();
+ add(checkBox, BorderLayout.WEST);
+ add(renderer, BorderLayout.CENTER);
+ return this;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * CheckTreeManager.java
+ */
+package net.sf.openrocket.gui.print.components;
+
+import javax.swing.JCheckBox;
+import javax.swing.JTree;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.TreePath;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+/**
+ * This class manages mouse clicks within the JTree, handling selection/deselections within the JCheckBox of each cell in the tree.
+ */
+public class CheckTreeManager extends MouseAdapter implements TreeSelectionListener {
+
+ /** The selection model. */
+ private CheckTreeSelectionModel selectionModel;
+ /** The actual JTree instance. */
+ private JTree tree;
+ /** The number of pixels of width of the check box. Clicking anywhere within the box will trigger actions. */
+ int hotspot = new JCheckBox().getPreferredSize().width;
+
+ /**
+ * Construct a check box tree manager.
+ *
+ * @param theTree the actual tree being managed
+ */
+ public CheckTreeManager (RocketPrintTree theTree) {
+ tree = theTree;
+ selectionModel = new CheckTreeSelectionModel(tree.getModel());
+ theTree.setCheckBoxSelectionModel(selectionModel);
+ tree.setCellRenderer(new CheckTreeCellRenderer((DefaultTreeCellRenderer)tree.getCellRenderer(), selectionModel));
+ tree.addMouseListener(this);
+ selectionModel.addTreeSelectionListener(this);
+
+ for (int x = 0; x < tree.getRowCount(); x++) {
+ tree.getSelectionModel().setSelectionPath(tree.getPathForRow(x));
+ }
+ }
+
+ public void addTreeSelectionListener (TreeSelectionListener tsl) {
+ selectionModel.addTreeSelectionListener(tsl);
+ }
+
+ /**
+ * Called when the mouse clicks within the tree.
+ *
+ * @param me the event that triggered this
+ */
+ @Override
+ public void mouseClicked (MouseEvent me) {
+ TreePath path = tree.getPathForLocation(me.getX(), me.getY());
+ if (path == null) {
+ return;
+ }
+ if (me.getX() > tree.getPathBounds(path).x + hotspot) {
+ return;
+ }
+
+ boolean selected = selectionModel.isPathSelected(path, true);
+ selectionModel.removeTreeSelectionListener(this);
+
+ try {
+ if (selected) {
+ selectionModel.removeSelectionPath(path);
+ }
+ else {
+ selectionModel.addSelectionPath(path);
+ }
+ }
+ finally {
+ selectionModel.addTreeSelectionListener(this);
+ tree.treeDidChange();
+ }
+ }
+
+ /**
+ * Get the selection model being used by this manager.
+ *
+ * @return the selection model
+ */
+ public CheckTreeSelectionModel getSelectionModel () {
+ return selectionModel;
+ }
+
+ /**
+ * Notify the tree that it changed.
+ *
+ * @param e unused
+ */
+ @Override
+ public void valueChanged (TreeSelectionEvent e) {
+ tree.treeDidChange();
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * CheckTreeSelectionModel.java
+ */
+package net.sf.openrocket.gui.print.components;
+
+import javax.swing.tree.DefaultTreeSelectionModel;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+import java.util.ArrayList;
+import java.util.Stack;
+
+/**
+ * This class implements the selection model for the checkbox tree. This specifically is used to keep
+ * track of the TreePaths that have a selected CheckBox.
+ */
+public class CheckTreeSelectionModel extends DefaultTreeSelectionModel {
+
+ /**
+ * The tree model.
+ */
+ private TreeModel model;
+
+ /**
+ * Constructor.
+ *
+ * @param theModel the model in use for the tree
+ */
+ public CheckTreeSelectionModel (TreeModel theModel) {
+ model = theModel;
+ setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
+ }
+
+ /**
+ * Tests whether there is any selected node in the subtree of given path.
+ *
+ * @param path the path to walk
+ *
+ * @return true if any item in the path or its descendants are selected
+ */
+ public boolean isPartiallySelected (TreePath path) {
+ if (isPathSelected(path, true)) {
+ return false;
+ }
+ TreePath[] selectionPaths = getSelectionPaths();
+ if (selectionPaths == null) {
+ return false;
+ }
+ for (TreePath selectionPath : selectionPaths) {
+ if (isDescendant(selectionPath, path)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tells whether given path is selected. If dig is true, then a path is assumed to be selected, if one of its
+ * ancestor is selected.
+ *
+ * @param path the path to interrogate
+ * @param dig if true then check if an ancestor is selected
+ *
+ * @return true if the path is selected
+ */
+ public boolean isPathSelected (TreePath path, boolean dig) {
+ if (!dig) {
+ return super.isPathSelected(path);
+ }
+ while (path != null && !super.isPathSelected(path)) {
+ path = path.getParentPath();
+ }
+ return path != null;
+ }
+
+ /**
+ * Determines if path1 is a descendant of path2.
+ *
+ * @param path1 descendant?
+ * @param path2 ancestor?
+ *
+ * @return true if path1 is a descendant of path2
+ */
+ private boolean isDescendant (TreePath path1, TreePath path2) {
+ Object obj1[] = path1.getPath();
+ Object obj2[] = path2.getPath();
+ for (int i = 0; i < obj2.length; i++) {
+ if (i < obj1.length) {
+ if (obj1[i] != obj2[i]) {
+ return false;
+ }
+ }
+ else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Unsupported exception.
+ *
+ * @param pPaths an array of paths
+ */
+ public void setSelectionPaths (TreePath[] pPaths) {
+ TreePath selected[] = getSelectionPaths();
+ for (TreePath aSelected : selected) {
+ removeSelectionPath(aSelected);
+ }
+ for (TreePath pPath : pPaths) {
+ addSelectionPath(pPath);
+ }
+ }
+
+ /**
+ * Add a set of TreePath nodes to the selection model.
+ *
+ * @param paths an array of tree path nodes
+ */
+ public void addSelectionPaths (TreePath[] paths) {
+ // deselect all descendants of paths[]
+ for (TreePath path : paths) {
+ TreePath[] selectionPaths = getSelectionPaths();
+ if (selectionPaths == null) {
+ break;
+ }
+ ArrayList<TreePath> toBeRemoved = new ArrayList<TreePath>();
+ for (TreePath selectionPath : selectionPaths) {
+ if (isDescendant(selectionPath, path)) {
+ toBeRemoved.add(selectionPath);
+ }
+ }
+ super.removeSelectionPaths(toBeRemoved.toArray(new TreePath[toBeRemoved.size()]));
+ }
+
+ // if all siblings are selected then deselect them and select parent recursively
+ // otherwise just select that path.
+ for (TreePath path : paths) {
+ TreePath temp = null;
+ while (areSiblingsSelected(path)) {
+ temp = path;
+ if (path.getParentPath() == null) {
+ break;
+ }
+ path = path.getParentPath();
+ }
+ if (temp != null) {
+ if (temp.getParentPath() != null) {
+ addSelectionPath(temp.getParentPath());
+ }
+ else {
+ if (!isSelectionEmpty()) {
+ removeSelectionPaths(getSelectionPaths());
+ }
+ super.addSelectionPaths(new TreePath[]{temp});
+ }
+ }
+ else {
+ super.addSelectionPaths(new TreePath[]{path});
+ }
+ }
+ }
+
+ /**
+ * Tells whether all siblings of given path are selected.
+ *
+ * @param path the tree path node
+ *
+ * @return true if all sibling nodes are selected
+ */
+ private boolean areSiblingsSelected (TreePath path) {
+ TreePath parent = path.getParentPath();
+ if (parent == null) {
+ return true;
+ }
+ Object node = path.getLastPathComponent();
+ Object parentNode = parent.getLastPathComponent();
+
+ int childCount = model.getChildCount(parentNode);
+ for (int i = 0; i < childCount; i++) {
+ Object childNode = model.getChild(parentNode, i);
+ if (childNode == node) {
+ continue;
+ }
+ if (!isPathSelected(parent.pathByAddingChild(childNode))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Remove paths from the selection model.
+ *
+ * @param paths the array of path nodes
+ */
+ public void removeSelectionPaths (TreePath[] paths) {
+ for (TreePath path : paths) {
+ if (path.getPathCount() == 1) {
+ super.removeSelectionPaths(new TreePath[]{path});
+ }
+ else {
+ toggleRemoveSelection(path);
+ }
+ }
+ }
+
+ /**
+ * If any ancestor node of given path is selected then deselect it and selection all its descendants except given
+ * path and descendants. otherwise just deselect the given path.
+ *
+ * @param path the tree path node
+ */
+ private void toggleRemoveSelection (TreePath path) {
+ Stack<TreePath> stack = new Stack<TreePath>();
+ TreePath parent = path.getParentPath();
+ while (parent != null && !isPathSelected(parent)) {
+ stack.push(parent);
+ parent = parent.getParentPath();
+ }
+ if (parent != null) {
+ stack.push(parent);
+ }
+ else {
+ super.removeSelectionPaths(new TreePath[]{path});
+ return;
+ }
+
+ while (!stack.isEmpty()) {
+ TreePath temp = stack.pop();
+ TreePath peekPath = stack.isEmpty() ? path : stack.peek();
+ Object node = temp.getLastPathComponent();
+ Object peekNode = peekPath.getLastPathComponent();
+ int childCount = model.getChildCount(node);
+ for (int i = 0; i < childCount; i++) {
+ Object childNode = model.getChild(node, i);
+ if (childNode != peekNode) {
+ super.addSelectionPaths(new TreePath[]{temp.pathByAddingChild(childNode)});
+ }
+ }
+ }
+ super.removeSelectionPaths(new TreePath[]{parent});
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * RocketPrintTree.java
+ */
+package net.sf.openrocket.gui.print.components;
+
+import net.sf.openrocket.gui.print.OpenRocketPrintable;
+import net.sf.openrocket.gui.print.PrintableContext;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Stage;
+
+import javax.swing.JTree;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeWillExpandListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.ExpandVetoException;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * A specialized JTree for displaying various rocket items that can be printed.
+ */
+public class RocketPrintTree extends JTree {
+
+ /**
+ * All check boxes are initially set to true (selected).
+ */
+ public static final boolean INITIAL_CHECKBOX_SELECTED = true;
+
+ /**
+ * The selection model that tracks the check box state.
+ */
+ private TreeSelectionModel theCheckBoxSelectionModel;
+
+ /**
+ * Constructor.
+ *
+ * @param root the vector of check box nodes (rows) to place into the tree
+ */
+ private RocketPrintTree (Vector root) {
+ super(root);
+
+ //Remove the little down and sideways arrows. These are not needed because the tree expansion is fixed.
+ ((javax.swing.plaf.basic.BasicTreeUI) this.getUI()).
+ setExpandedIcon(null);
+ ((javax.swing.plaf.basic.BasicTreeUI) this.getUI()).
+ setCollapsedIcon(null);
+ }
+
+ /**
+ * Factory method to create a specialized JTree. This version is for rocket's that have more than one stage.
+ *
+ * @param rocketName the name of the rocket
+ * @param stages the array of all stages
+ *
+ * @return an instance of JTree
+ */
+ public static RocketPrintTree create (String rocketName, RocketComponent[] stages) {
+ Vector root = new Vector();
+ Vector toAddTo = root;
+
+ if (stages != null) {
+ if (stages.length > 1) {
+ final Vector parent = new NamedVector(rocketName != null ? rocketName : "Rocket");
+
+ root.add(parent);
+ toAddTo = parent;
+ }
+ for (RocketComponent stage : stages) {
+ if (stage instanceof Stage) {
+ toAddTo.add(createNamedVector(stage.getName(), createPrintTreeNode(true), stage.getStageNumber()));
+ }
+ }
+ }
+ toAddTo.add(new CheckBoxNode(OpenRocketPrintable.DESIGN_REPORT.getDescription(),
+ INITIAL_CHECKBOX_SELECTED));
+
+ RocketPrintTree tree = new RocketPrintTree(root);
+
+ tree.addTreeWillExpandListener
+ (new TreeWillExpandListener() {
+ public void treeWillExpand (TreeExpansionEvent e) {
+ }
+
+ public void treeWillCollapse (TreeExpansionEvent e)
+ throws ExpandVetoException {
+ throw new ExpandVetoException(e, "you can't collapse this JTree");
+ }
+ });
+
+ return tree;
+ }
+
+ /**
+ * Factory method to create a specialized JTree. This version is for a rocket with only one stage.
+ *
+ * @param rocketName the name of the rocket
+ *
+ * @return an instance of JTree
+ */
+ public static RocketPrintTree create (String rocketName) {
+ Vector root = new Vector();
+ root.add(new NamedVector(rocketName != null ? rocketName : "Rocket", createPrintTreeNode(false)));
+
+ RocketPrintTree tree = new RocketPrintTree(root);
+
+ tree.addTreeWillExpandListener
+ (new TreeWillExpandListener() {
+ public void treeWillExpand (TreeExpansionEvent e) {
+ }
+
+ public void treeWillCollapse (TreeExpansionEvent e)
+ throws ExpandVetoException {
+ throw new ExpandVetoException(e, "you can't collapse this JTree");
+ }
+ });
+
+ return tree;
+ }
+
+ /**
+ * This tree needs to have access both to the normal selection model (for the textual row) which is managed by the
+ * superclass, as well as the selection model for the check boxes. This mutator method allows an external class to
+ * set the model back onto this class. Because of some unfortunate circular dependencies this cannot be set at
+ * construction.
+ * <p/>
+ * TODO: Ensure these circular references get cleaned up properly at dialog disposal so everything can be GC'd.
+ *
+ * @param checkBoxSelectionModel the selection model used to keep track of the check box state
+ */
+ public void setCheckBoxSelectionModel (TreeSelectionModel checkBoxSelectionModel) {
+ theCheckBoxSelectionModel = checkBoxSelectionModel;
+ }
+
+ /**
+ * Add a selection path to the internal check box selection model. The normal JTree selection model is unaffected.
+ * This has the effect of "selecting" the check box, but not highlighting the row.
+ *
+ * @param path the path (row)
+ */
+ public void addSelectionPath (TreePath path) {
+ theCheckBoxSelectionModel.addSelectionPath(path);
+ }
+
+ /**
+ * Helper to construct a named vector.
+ *
+ * @param name the name of the vector
+ * @param nodes the array of nodes to put into the vector
+ * @param stage the stage number
+ *
+ * @return a NamedVector suitable for adding to a JTree
+ */
+ private static Vector createNamedVector (String name, CheckBoxNode[] nodes, int stage) {
+ return new NamedVector(name, nodes, stage);
+ }
+
+ /**
+ * Helper to construct the set of check box rows for each stage.
+ *
+ * @param onlyStageSpecific if true then only stage specific OpenRocketPrintable rows are represented (in this part
+ * of the tree).
+ *
+ * @return an array of CheckBoxNode
+ */
+ private static CheckBoxNode[] createPrintTreeNode (boolean onlyStageSpecific) {
+ List<CheckBoxNode> nodes = new ArrayList<CheckBoxNode>();
+ OpenRocketPrintable[] printables = OpenRocketPrintable.values();
+ for (OpenRocketPrintable openRocketPrintable : printables) {
+ if (!onlyStageSpecific || openRocketPrintable.isStageSpecific() ) {
+ nodes.add(new CheckBoxNode(openRocketPrintable.getDescription(),
+ INITIAL_CHECKBOX_SELECTED));
+ }
+ }
+ return nodes.toArray(new CheckBoxNode[nodes.size()]);
+ }
+
+ /**
+ * Get the set of items to be printed, as selected by the user.
+ *
+ * @return the things to be printed, returned as an Iterator<PrintableContext>
+ */
+ public Iterator<PrintableContext> getToBePrinted () {
+ final DefaultMutableTreeNode mutableTreeNode = (DefaultMutableTreeNode) getModel().getRoot();
+ PrintableContext pc = new PrintableContext();
+ add(pc, mutableTreeNode);
+ return pc.iterator();
+ }
+
+ /**
+ * Walk a tree, finding everything that has been selected and aggregating it into something that can be iterated upon
+ * This method is recursive.
+ *
+ * @param pc the printable context that aggregates the choices into an iterator
+ * @param theMutableTreeNode the root node
+ */
+ private void add (final PrintableContext pc, final DefaultMutableTreeNode theMutableTreeNode) {
+ int children = theMutableTreeNode.getChildCount();
+ for (int x = 0; x < children; x++) {
+
+ final DefaultMutableTreeNode at = (DefaultMutableTreeNode) theMutableTreeNode.getChildAt(x);
+ if (at.getUserObject() instanceof CheckBoxNode) {
+ CheckBoxNode cbn = (CheckBoxNode) at.getUserObject();
+ if (cbn.isSelected()) {
+ final OpenRocketPrintable printable = OpenRocketPrintable.findByDescription(cbn.getText());
+ pc.add(
+ printable.isStageSpecific() ? ((NamedVector) theMutableTreeNode.getUserObject())
+ .getStage() : null,
+ printable);
+ }
+ }
+ add(pc, at);
+ }
+ }
+
+}
+
+/**
+ * JTree's work off of Vector's (unfortunately). This class is tailored for use with check boxes in the JTree.
+ */
+class NamedVector extends Vector<CheckBoxNode> {
+ String name;
+
+ int stageNumber;
+
+ public NamedVector (String theName) {
+ name = theName;
+ }
+
+ public NamedVector (String theName, CheckBoxNode elements[], int stage) {
+ this(theName, elements);
+ stageNumber = stage;
+ }
+
+ public NamedVector (String theName, CheckBoxNode elements[]) {
+ name = theName;
+ for (int i = 0, n = elements.length; i < n; i++) {
+ add(elements[i]);
+ }
+ }
+
+ public String toString () {
+ return name;
+ }
+
+ public int getStage () {
+ return stageNumber;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * BaseVisitorStrategy.java
+ */
+package net.sf.openrocket.gui.print.visitor;
+
+import com.itextpdf.text.Document;
+import com.itextpdf.text.pdf.PdfWriter;
+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 java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 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) {
+ 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 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
--- /dev/null
+/*
+ * FinSetVisitorStrategy.java
+ */
+package net.sf.openrocket.gui.print.visitor;
+
+import com.itextpdf.text.Document;
+import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.Rectangle;
+import com.itextpdf.text.pdf.PdfContentByte;
+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.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.FreeformFinSet;
+import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.util.Set;
+
+/**
+ * A visitor strategy for drawing fin templates.
+ */
+public class FinSetVisitorStrategy extends BaseVisitorStrategy {
+
+ /**
+ * Constructor.
+ *
+ * @param doc The iText document
+ * @param theWriter The direct iText writer
+ * @param theStagesToVisit The stages to be visited by this strategy
+ */
+ public FinSetVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
+ super(doc, theWriter, theStagesToVisit);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void visit (final TrapezoidFinSet visitable) {
+ doVisit(visitable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void visit (final EllipticalFinSet visitable) {
+ doVisit(visitable);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void visit (final FreeformFinSet visitable) {
+ 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 FinSet visitable) {
+ if (shouldVisitStage(visitable.getStageNumber())) {
+ try {
+ PrintableFinSet pfs = new PrintableFinSet(visitable);
+
+ java.awt.Dimension finSize = pfs.getSize();
+ final Dimension pageSize = getPageSize();
+ if (fitsOnOnePage(pageSize, finSize.getWidth(), finSize.getHeight())) {
+ printOnOnePage(pfs);
+ }
+ else {
+ BufferedImage image = (BufferedImage) pfs.createImage();
+ ITextHelper.renderImageAcrossPages(new Rectangle(pageSize.getWidth(), pageSize.getHeight()),
+ document, writer, image);
+ }
+ }
+ catch (DocumentException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Determine if the image will fit on the given page.
+ *
+ * @param pageSize the page size
+ * @param wImage the width of the thing to be printed
+ * @param hImage the height of the thing to be printed
+ *
+ * @return true if the thing to be printed will fit on a single page
+ */
+ private boolean fitsOnOnePage (Dimension pageSize, double wImage, double hImage) {
+ double wPage = pageSize.getWidth();
+ double hPage = pageSize.getHeight();
+
+ int wRatio = (int) Math.ceil(wImage / wPage);
+ int hRatio = (int) Math.ceil(hImage / hPage);
+
+ return wRatio <= 1.0d && hRatio <= 1.0d;
+ }
+
+ /**
+ * Print the fin set.
+ *
+ * @param thePfs the printable fin set
+ */
+ private void printOnOnePage (final PrintableFinSet thePfs) {
+ Dimension d = getPageSize();
+ PdfContentByte cb = writer.getDirectContent();
+ Graphics2D g2 = cb.createGraphics(d.width, d.height);
+ thePfs.print(g2);
+ g2.dispose();
+ document.newPage();
+ }
+}
+
--- /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;
+ }
+
+}
+
+
--- /dev/null
+/*
+ * PartsDetailVisitorStrategy.java
+ */
+package net.sf.openrocket.gui.print.visitor;
+
+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 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.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.unit.Unit;
+import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.Coordinate;
+
+import javax.swing.ImageIcon;
+import java.io.IOException;
+import java.text.NumberFormat;
+import java.util.Collection;
+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) {
+ }
+
+ 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()));
+
+ RocketComponent[] rc = visitable.getChildren();
+ goDeep(rc);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void visit (final BodyComponent visitable) {
+ grid.addCell(visitable.getName());
+ grid.completeRow();
+ 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()));
+
+ 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()));
+
+ 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()));
+
+ 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()));
+ 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()));
+ 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()));
+ 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()));
+ 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()));
+
+ 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()) + ")";
+ }
+
+}
--- /dev/null
+/*
+ * PartsListVisitorStrategy.java
+ */
+package net.sf.openrocket.gui.print.visitor;
+
+import com.itextpdf.text.Document;
+import com.itextpdf.text.pdf.PdfWriter;
+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 java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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();
+ 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);
+ }
+ }
+
+}
+
+class PartsAccumulator {
+
+ int quantity = 0;
+
+ RocketComponent component;
+
+ PartsAccumulator (RocketComponent theComponent) {
+ component = theComponent;
+ }
+
+ void increment() {
+ quantity++;
+ }
+
+ int quantity() {
+ return quantity;
+ }
+
+ 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;
+ }
+
+ 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 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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;
+ 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) {
+ 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 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.
+ */
+ public void close () {
+ if (mass > 0d) {
+ stageComponents.add(mass);
+ }
+ }
+
+}
package net.sf.openrocket.gui.rocketfigure;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.Transformation;
+
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.Transformation;
-
public class BodyTubeShapes extends RocketComponentShapes {
net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube)component;
double length = tube.getLength();
- double radius = tube.getRadius();
+ double radius = tube.getOuterRadius();
Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0)));
Shape[] s = new Shape[start.length];
Transformation transformation) {
net.sf.openrocket.rocketcomponent.BodyTube tube = (net.sf.openrocket.rocketcomponent.BodyTube)component;
- double or = tube.getRadius();
+ double or = tube.getOuterRadius();
Coordinate[] start = transformation.transform(tube.toAbsolute(new Coordinate(0,0,0)));
package net.sf.openrocket.gui.rocketfigure;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.Transformation;
+
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.Transformation;
-
public class LaunchLugShapes extends RocketComponentShapes {
net.sf.openrocket.rocketcomponent.LaunchLug lug = (net.sf.openrocket.rocketcomponent.LaunchLug)component;
double length = lug.getLength();
- double radius = lug.getRadius();
+ double radius = lug.getOuterRadius();
Coordinate[] start = transformation.transform(lug.toAbsolute(new Coordinate(0,0,0)));
Shape[] s = new Shape[start.length];
Transformation transformation) {
net.sf.openrocket.rocketcomponent.LaunchLug lug = (net.sf.openrocket.rocketcomponent.LaunchLug)component;
- double or = lug.getRadius();
+ double or = lug.getOuterRadius();
Coordinate[] start = transformation.transform(lug.toAbsolute(new Coordinate(0,0,0)));
package net.sf.openrocket.gui.scalefigure;
+import net.sf.openrocket.gui.figureelements.FigureElement;
+import net.sf.openrocket.gui.main.ExceptionHandler;
+import net.sf.openrocket.motor.Motor;
+import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.MotorMount;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.util.BugException;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.LineStyle;
+import net.sf.openrocket.util.MathUtil;
+import net.sf.openrocket.util.Prefs;
+import net.sf.openrocket.util.Reflection;
+import net.sf.openrocket.util.Transformation;
+
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.util.Iterator;
import java.util.LinkedHashSet;
-import net.sf.openrocket.gui.figureelements.FigureElement;
-import net.sf.openrocket.gui.main.ExceptionHandler;
-import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.Configuration;
-import net.sf.openrocket.rocketcomponent.MotorMount;
-import net.sf.openrocket.rocketcomponent.RocketComponent;
-import net.sf.openrocket.util.BugException;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.LineStyle;
-import net.sf.openrocket.util.MathUtil;
-import net.sf.openrocket.util.Prefs;
-import net.sf.openrocket.util.Reflection;
-import net.sf.openrocket.util.Transformation;
-
/**
* A <code>ScaleFigure</code> that draws a complete rocket. Extra information can
* be added to the figure by the methods {@link #addRelativeExtra(FigureElement)},
tx = BORDER_PIXELS_WIDTH - minX*scale;
}
-
- if (figureHeightPx + 2*BORDER_PIXELS_HEIGHT < getHeight()) {
- ty = getHeight()/2;
- } else {
- ty = BORDER_PIXELS_HEIGHT + figureHeightPx/2;
- }
-
- if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) {
+
+ ty = computeTy(figureHeightPx);
+
+ if (Math.abs(translateX - tx)>1 || Math.abs(translateY - ty)>1) {
// Origin has changed, fire event
translateX = tx;
translateY = ty;
}
}
-
-
- public RocketComponent[] getComponentsByPoint(double x, double y) {
+
+ protected double computeTy (int heightPx) {
+ final double ty;
+ if (heightPx + 2*BORDER_PIXELS_HEIGHT < getHeight()) {
+ ty = getHeight()/2;
+ } else {
+ ty = BORDER_PIXELS_HEIGHT + heightPx/2;
+ }
+ return ty;
+ }
+
+
+ public RocketComponent[] getComponentsByPoint(double x, double y) {
// Calculate point in shapes' coordinates
Point2D.Double p = new Point2D.Double(x,y);
try {
package net.sf.openrocket.gui.scalefigure;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.Point;
-import java.awt.event.ActionEvent;
-import java.awt.event.InputEvent;
-import java.awt.event.MouseEvent;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
-import javax.swing.AbstractAction;
-import javax.swing.Action;
-import javax.swing.JComboBox;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JSlider;
-import javax.swing.JToggleButton;
-import javax.swing.JViewport;
-import javax.swing.SwingUtilities;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-import javax.swing.event.TreeSelectionEvent;
-import javax.swing.event.TreeSelectionListener;
-import javax.swing.tree.TreePath;
-import javax.swing.tree.TreeSelectionModel;
-
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Prefs;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.JToggleButton;
+import javax.swing.JViewport;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
/**
* A JPanel that contains a RocketFigure and buttons to manipulate the figure.
*
public Configuration getConfiguration() {
return configuration;
}
-
- public void setSelectionModel(TreeSelectionModel m) {
+
+ /**
+ * Get the center of pressure figure element.
+ *
+ * @return center of pressure info
+ */
+ public Caret getExtraCP () {
+ return extraCP;
+ }
+
+ /**
+ * Get the center of gravity figure element.
+ *
+ * @return center of gravity info
+ */
+ public Caret getExtraCG () {
+ return extraCG;
+ }
+
+ /**
+ * Get the extra text figure element.
+ *
+ * @return extra text that contains info about the rocket design
+ */
+ public RocketInfo getExtraText () {
+ return extraText;
+ }
+
+ public void setSelectionModel(TreeSelectionModel m) {
if (selectionModel != null) {
selectionModel.removeTreeSelectionListener(this);
}
}
-
/**
* Get the outer radius of the component at cylindrical coordinate (x,theta).
*
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.motor.Motor;
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 {
+public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial {
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
*/
- public double getRadius() {
+ @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
*/
- public void setRadius(double radius) {
+ @Override
+ public void setOuterRadius (double radius) {
if ((this.radius == radius) && (autoRadius == false))
return;
@Override
- public double getAftRadius() {
- return getRadius();
- }
-
+ public double getAftRadius() { return getOuterRadius(); }
@Override
- public double getForeRadius() {
- return getRadius();
- }
-
+ public double getForeRadius() { return getOuterRadius(); }
@Override
public boolean isAftRadiusAutomatic() {
return isRadiusAutomatic();
return -1;
}
}
- return getRadius();
+ return getOuterRadius();
}
@Override
return -1;
}
}
- return getRadius();
+ return getOuterRadius();
}
-
+
+
+
+
+ @Override
public double getInnerRadius() {
if (filled)
return 0;
- return Math.max(getRadius() - thickness, 0);
+ return Math.max(getOuterRadius()-thickness, 0);
}
+ @Override
public void setInnerRadius(double r) {
- setThickness(getRadius() - r);
+ setThickness(getOuterRadius()-r);
}
public String getComponentName() {
return "Body tube";
}
-
-
+
+ /**
+ * 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
/**
- * Returns the outer radius at the position x. This returns the same value as getRadius().
+ * Returns the outer radius at the position x. This returns the same value as getOuterRadius().
*/
@Override
public double getRadius(double x) {
- return getRadius();
+ return getOuterRadius();
}
/**
if (filled)
return 0.0;
else
- return Math.max(getRadius() - thickness, 0);
+ return Math.max(getOuterRadius()-thickness,0);
}
*/
@Override
public double getComponentVolume() {
- double r = getRadius();
+ double r = getOuterRadius();
if (filled)
return getFilledVolume(r, length);
else
@Override
public double getLongitudalUnitInertia() {
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
- return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getRadius()) + 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(getRadius())) / 2;
+ return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius()))/2;
}
@Override
public Collection<Coordinate> getComponentBounds() {
Collection<Coordinate> bounds = new ArrayList<Coordinate>(8);
- double r = getRadius();
+ double r = getOuterRadius();
addBound(bounds, 0, r);
addBound(bounds, length, r);
return bounds;
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
+/*
+ * Coaxial.java
+ */
+package net.sf.openrocket.rocketcomponent;
+
+/**
+ * This interface defines the API for components that are axially
+ * symmetric. It differs from RadialParent in that RadialParent applies
+ * to axially symmetric components whose radius varies with position, while
+ * this interface is for components that have a constant radius over it's length.
+ */
+public interface Coaxial {
+
+ /**
+ * Get the length of the radius of the inside dimension, in standard units.
+ *
+ * @return the inner radius
+ */
+ double getInnerRadius();
+
+ /**
+ * Set the length of the radius of the inside dimension, in standard units.
+ *
+ * @param v the length of the inner radius
+ */
+ void setInnerRadius(double v);
+
+ /**
+ * Get the length of the radius of the outside dimension, in standard units.
+ *
+ * @return the outer radius
+ */
+ double getOuterRadius();
+
+ /**
+ * Set the length of the radius of the outside dimension, in standard units.
+ *
+ * @param v the length of the outer radius
+ */
+ void setOuterRadius(double v);
+
+ /**
+ * Get the wall thickness of the component. Typically this is just
+ * the outer radius - inner radius.
+ *
+ * @return the thickness of the wall
+ */
+ double getThickness();
+
+}
--- /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 ();
+}
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);
+ }
+
}
this.material = src.material;
}
+ /**
+ * 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);
+ }
+
}
package net.sf.openrocket.rocketcomponent;
-import java.util.ArrayList;
-import java.util.Arrays;
-
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
+import java.util.ArrayList;
+import java.util.Arrays;
+
public class FreeformFinSet extends FinSet {
private static final LogHelper log = Application.getLogger();
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();
package net.sf.openrocket.rocketcomponent;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.util.BugException;
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
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);
+ }
package net.sf.openrocket.rocketcomponent;
+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;
-public class LaunchLug extends ExternalComponent {
-
+public class LaunchLug extends ExternalComponent implements Coaxial {
+
private double radius;
private double thickness;
}
- public double getRadius() {
+ public double getOuterRadius () {
return radius;
}
- public void setRadius(double radius) {
+ public void setOuterRadius (double radius) {
if (MathUtil.equals(this.radius, radius))
return;
this.radius = radius;
}
public void setInnerRadius(double innerRadius) {
- setRadius(innerRadius + thickness);
+ setOuterRadius(innerRadius + thickness);
}
public double getThickness() {
@Override
public double getLongitudalUnitInertia() {
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
- return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getRadius()) + 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(getRadius())) / 2;
+ return (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius()))/2;
}
@Override
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;
/**
- * A MassObject is an internal component that can a specific weight, but not
- * necessarily a strictly bound shape. It is represented as a homogeneous
- * cylinder and drawn in the rocket figure with rounded corners.
- * <p>
- * Subclasses of this class need only implement the {@link #getComponentMass()},
- * {@link #getComponentName()} and {@link #isCompatible(RocketComponent)}
- * methods.
- *
+ * A MassObject is an internal component that can a specific weight, but not necessarily a strictly bound shape. It is
+ * represented as a homogeneous cylinder and drawn in the rocket figure with rounded corners.
+ * <p/>
+ * Subclasses of this class need only implement the {@link #getComponentMass()}, {@link #getComponentName()} and {@link
+ * #isCompatible(RocketComponent)} methods.
+ *
* @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))
- return;
- 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))
- return;
- 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))
- return;
- this.radialPosition = radialPosition;
- shiftY = radialPosition * Math.cos(radialDirection);
- 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))
- return;
- this.radialDirection = radialDirection;
- shiftY = radialPosition * Math.cos(radialDirection);
- shiftZ = radialPosition * Math.sin(radialDirection);
- fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
- }
-
-
-
-
- /**
- * Shift the coordinates according to the radial position and direction.
- */
- @Override
- public final Coordinate[] shiftCoordinates(Coordinate[] array) {
- for (int i=0; i < array.length; i++) {
- array[i] = array[i].add(0, shiftY, shiftZ);
- }
- return array;
- }
-
- @Override
- public final Coordinate getComponentCG() {
- return new Coordinate(length/2, shiftY, shiftZ, getComponentMass());
- }
-
- @Override
- public final double getLongitudalUnitInertia() {
- 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, 0, radius);
- addBound(c, length, radius);
- return c;
- }
+ 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)) {
+ return;
+ }
+ 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)) {
+ return;
+ }
+ 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)) {
+ return;
+ }
+ this.radialPosition = radialPosition;
+ shiftY = radialPosition * Math.cos(radialDirection);
+ 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)) {
+ return;
+ }
+ this.radialDirection = radialDirection;
+ shiftY = radialPosition * Math.cos(radialDirection);
+ shiftZ = radialPosition * Math.sin(radialDirection);
+ fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
+ }
+
+
+ /**
+ * Shift the coordinates according to the radial position and direction.
+ */
+ @Override
+ public final Coordinate[] shiftCoordinates (Coordinate[] array) {
+ for (int i = 0; i < array.length; i++) {
+ array[i] = array[i].add(0, shiftY, shiftZ);
+ }
+ return array;
+ }
+
+ @Override
+ public final Coordinate getComponentCG () {
+ return new Coordinate(length / 2, shiftY, shiftZ, getComponentMass());
+ }
+
+ @Override
+ public final double getLongitudalUnitInertia () {
+ 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, 0, radius);
+ 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);
+ }
+
}
public String getComponentName() {
return "Nose cone";
}
+
+ /**
+ * 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);
+ }
}
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-public abstract class RadiusRingComponent extends RingComponent {
+public abstract class RadiusRingComponent extends RingComponent implements Coaxial {
protected double outerRadius = 0;
protected double innerRadius = 0;
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);
+ }
+
}
package net.sf.openrocket.rocketcomponent;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
-
/**
* An inner component that consists of a hollow cylindrical component. This can be
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
-public abstract class RingComponent extends StructuralComponent {
+public abstract class RingComponent extends StructuralComponent implements Coaxial {
protected boolean outerRadiusAutomatic = false;
protected boolean innerRadiusAutomatic = false;
-
+ @Override
public abstract double getOuterRadius();
+ @Override
public abstract void setOuterRadius(double r);
+ @Override
public abstract double getInnerRadius();
+ @Override
public abstract void setInnerRadius(double r);
+ @Override
public abstract double getThickness();
public abstract void setThickness(double thickness);
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);
+ }
+
}
package net.sf.openrocket.rocketcomponent;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-
-import javax.swing.event.ChangeListener;
-import javax.swing.event.EventListenerList;
-
import net.sf.openrocket.gui.main.ExceptionHandler;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.UniqueID;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.EventListenerList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
/**
* Base for all rocket components. This is the "starting point" for all rocket trees.
}
-
/**
* 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
public boolean isCompatible(Class<? extends RocketComponent> type) {
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);
+ }
}
package net.sf.openrocket.rocketcomponent;
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EmptyStackException;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Stack;
-
-import javax.swing.event.ChangeListener;
-
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.logging.TraceException;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.UniqueID;
+import javax.swing.event.ChangeListener;
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EmptyStackException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Stack;
+
public abstract class RocketComponent implements ChangeSource, Cloneable,
- Iterable<RocketComponent> {
+ Iterable<RocketComponent> , Visitable<ComponentVisitor, RocketComponent> {
private static final LogHelper log = Application.getLogger();
/*
this.relativePosition = relativePosition;
newID();
}
-
-
+ //////////// Methods that must be implemented ////////////
- //////////// Methods that must be implemented ////////////
-
-
/**
* Static component name. The name may not vary of the parameters, it must be static.
*/
}
+ /**
+ * 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 ////////////
public class Stage extends ComponentAssembly {
- @Override
- public String getComponentName() {
- return "Stage";
- }
+ @Override
+ public String getComponentName () {
+ return "Stage";
+ }
@Override
return true;
}
- /**
+ /**
* 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.
- * @return Whether such a component can be added.
- */
- @Override
- public boolean isCompatible(Class<? extends RocketComponent> type) {
- return BodyComponent.class.isAssignableFrom(type);
- }
-
+ *
+ * @param type The RocketComponent class type to add.
+ *
+ * @return Whether such a component can be added.
+ */
+ @Override
+ 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.Chars.*;
-import static net.sf.openrocket.util.MathUtil.*;
+import net.sf.openrocket.util.Coordinate;
+import net.sf.openrocket.util.MathUtil;
import java.util.Collection;
-import net.sf.openrocket.util.Coordinate;
-import net.sf.openrocket.util.MathUtil;
+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 {
/**
* Numerically solve clipLength from the equation
* r1 == type.getRadius(clipLength,r2,clipLength+length)
- * using a binary search. It assumes getRadius() to be monotonically increasing.
+ * using a binary search. It assumes getOuterRadius() to be monotonically increasing.
*/
private void calculateClip(double r1, double r2) {
double min = 0, max = length;
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
return "Trapezoidal fin set";
}
+ /**
+ * 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);
+ }
}
--- /dev/null
+/*
+ * Visitable.java
+ */
+package net.sf.openrocket.rocketcomponent;
+
+/**
+ * This interface describes a portion of the Visitor pattern, using generics to assure type-safety.
+ * The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors,
+ * while these visitors are only able to visit the elements of that hierarchy.
+ *
+ * The key concept regarding the Visitor pattern is to realize that Java will only ÒdiscriminateÓ the type of an
+ * object being called, not the type of an object being passed.
+ *
+ * In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an
+ * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes ÒknownÓ but the
+ * concrete type of the argument is still unknown. <code>visit</code> is then called on the parameter object, passing
+ * the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the
+ * type (and identity) of both objects are known.
+ *
+ * Specifically, this interface is to be implemented by every class in the RocketComponent hierarchy that
+ * can be visited AND which are sufficiently specialized from their super class. If they only provide
+ * constraints to their superclass (such as TubeCoupler), then the implementation of this interface at
+ * the superclass level is sufficient.
+ *
+ * Admittedly, the syntax is a bit contorted here, as it is necessarily self-referential for type-safety.
+ *
+ * <V> The visitor type
+ * <T> The visitable (the concrete class that implements this interface)
+ */
+public interface Visitable<V extends Visitor<V, T>, T extends Visitable<V, T>> {
+
+ /**
+ * Any class in the hierarchy that allows itself to be visited will implement this method. The normal
+ * behavior is that the visitor will invoke this method of a Visitable, passing itself. The Visitable
+ * turns around calls the Visitor back. This idiom is also known as 'double-dispatching'.
+ *
+ * @param visitor the visitor that will be called back
+ */
+ public void accept(V visitor);
+
+}
--- /dev/null
+/*
+ * Visitor.java
+ */
+package net.sf.openrocket.rocketcomponent;
+
+/**
+ * This interface describes a portion of the Visitor pattern, using generics to assure type-safety.
+ * The elements of the concrete object hierarchy are only visitable by an associated hierarchy of visitors,
+ * while these visitors are only able to visit the elements of that hierarchy.
+ *
+ * The key concept regarding the Visitor pattern is to realize that Java will only ÒdiscriminateÓ the type of an
+ * object being called, not the type of an object being passed.
+ *
+ * In order for the type of two objects to be determinable to the JVM, each object must be the receiver of an
+ * invocation. Here, when accept is called on a Visitable, the concrete type of the Visitable becomes ÒknownÓ but the
+ * concrete type of the argument is still unknown. <code>visit</code> is then called on the parameter object, passing
+ * the Visitable back, which has type and identity. Flow of control has now been 'double-dispatched' such that the
+ * type (and identity) of both objects are known.
+ *
+ * Specifically, this interface is to be implemented by every class in the RocketComponent hierarchy that
+ * can be visited AND which are sufficiently specialized from their super class. If they only provide
+ * constraints to their superclass (such as TubeCoupler), then the implementation of this interface at
+ * the superclass level is sufficient.
+ *
+ * Admittedly, the syntax is a bit contorted here, as it is necessarily self-referential for type-safety.
+ *
+ * <V> The visitor type (the concrete class that implements this interface)
+ * <T> The visitable
+ */
+public interface Visitor<V extends Visitor<V, T>, T extends Visitable<V, T>> {
+
+ /**
+ * The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a
+ * corresponding <code>accept</code>.
+ *
+ * @param visitable the instance of the Visitable (the target of what is being visiting)
+ */
+ void visit(T visitable);
+}
\ No newline at end of file
package net.sf.openrocket.util;
-import java.awt.Color;
-import java.util.Random;
-
import net.sf.openrocket.material.Material;
import net.sf.openrocket.material.Material.Type;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.Bulkhead;
import net.sf.openrocket.rocketcomponent.CenteringRing;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
+import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
+import net.sf.openrocket.rocketcomponent.FinSet.CrossSection;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
import net.sf.openrocket.rocketcomponent.InnerTube;
import net.sf.openrocket.rocketcomponent.InternalComponent;
import net.sf.openrocket.rocketcomponent.LaunchLug;
import net.sf.openrocket.rocketcomponent.MassComponent;
+import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent;
import net.sf.openrocket.rocketcomponent.NoseCone;
import net.sf.openrocket.rocketcomponent.ReferenceType;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
import net.sf.openrocket.rocketcomponent.Stage;
import net.sf.openrocket.rocketcomponent.Transition;
+import net.sf.openrocket.rocketcomponent.Transition.Shape;
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.rocketcomponent.TubeCoupler;
-import net.sf.openrocket.rocketcomponent.ExternalComponent.Finish;
-import net.sf.openrocket.rocketcomponent.FinSet.CrossSection;
-import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent;
-import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
-import net.sf.openrocket.rocketcomponent.Transition.Shape;
import net.sf.openrocket.startup.Application;
+import java.awt.Color;
+import java.util.Random;
+
public class TestRockets {
private final String key;
body.setLength(rnd(0.3));
body.setMotorMount(rnd.nextBoolean());
body.setMotorOverhang(rnd.nextGaussian() * 0.03);
- body.setRadius(rnd(0.06));
+ body.setOuterRadius(rnd(0.06));
body.setRadiusAutomatic(rnd.nextBoolean());
stage.addChild(body);
WarningSet warnings = new WarningSet();
handler.closeElement("OD", attributes, "-1", warnings);
- assertEquals(0d, component.getRadius());
+ assertEquals(0d, component.getOuterRadius());
handler.closeElement("OD", attributes, "0", warnings);
- assertEquals(0d, component.getRadius());
+ assertEquals(0d, component.getOuterRadius());
handler.closeElement("OD", attributes, "75", warnings);
- assertEquals(75d / RocksimHandler.ROCKSIM_TO_OPENROCKET_RADIUS, component.getRadius());
+ assertEquals(75d / RocksimHandler.ROCKSIM_TO_OPENROCKET_RADIUS, component.getOuterRadius());
handler.closeElement("OD", attributes, "foo", warnings);
assertEquals(1, warnings.size());
warnings.clear();