From 2c2a8ed115fe09479de9c271d68c5a86bd17a489 Mon Sep 17 00:00:00 2001 From: rodinia814 Date: Tue, 7 Sep 2010 04:46:39 +0000 Subject: [PATCH] DGP - 1st printing git-svn-id: https://openrocket.svn.sourceforge.net/svnroot/openrocket/branches/printing@79 180e2498-e6e9-4542-8430-84ac67f01cd8 --- .classpath | 1 + .../aerodynamics/barrowman/LaunchLugCalc.java | 4 +- .../file/openrocket/OpenRocketLoader.java | 33 +- .../file/openrocket/savers/BodyTubeSaver.java | 2 +- .../openrocket/savers/LaunchLugSaver.java | 6 +- .../file/rocksim/BodyTubeHandler.java | 2 +- .../file/rocksim/LaunchLugHandler.java | 2 +- .../file/rocksim/ParachuteHandler.java | 2 +- .../gui/components/ColorChooser.java | 104 ++++ .../gui/configdialog/BodyTubeConfig.java | 12 +- .../gui/configdialog/LaunchLugConfig.java | 12 +- .../openrocket/gui/dialogs/PrintDialog.java | 270 +++++++++++ .../sf/openrocket/gui/dialogs/PrintPanel.java | 434 +++++++++++++++++ .../gui/figureelements/RocketInfo.java | 104 +++- .../sf/openrocket/gui/main/BasicFrame.java | 136 +++--- .../sf/openrocket/gui/print/DesignReport.java | 457 ++++++++++++++++++ .../sf/openrocket/gui/print/ITextHelper.java | 88 ++++ .../gui/print/OpenRocketPrintable.java | 87 ++++ .../gui/print/PDFPrintStreamDoc.java | 57 +++ .../sf/openrocket/gui/print/PaperSize.java | 128 +++++ .../openrocket/gui/print/PrintController.java | 121 +++++ .../sf/openrocket/gui/print/PrintFigure.java | 33 ++ .../gui/print/PrintSimulationWorker.java | 49 ++ .../sf/openrocket/gui/print/PrintUnit.java | 158 ++++++ .../openrocket/gui/print/PrintUtilities.java | 142 ++++++ .../gui/print/PrintableContext.java | 110 +++++ .../openrocket/gui/print/PrintableFinSet.java | 158 ++++++ .../gui/print/TemplateProperties.java | 49 ++ .../gui/print/components/CheckBoxNode.java | 77 +++ .../components/CheckTreeCellRenderer.java | 83 ++++ .../print/components/CheckTreeManager.java | 99 ++++ .../components/CheckTreeSelectionModel.java | 245 ++++++++++ .../gui/print/components/RocketPrintTree.java | 252 ++++++++++ .../print/visitor/BaseVisitorStrategy.java | 333 +++++++++++++ .../print/visitor/FinSetVisitorStrategy.java | 76 +++ .../visitor/MotorMountVisitorStrategy.java | 100 ++++ .../visitor/PartsDetailVisitorStrategy.java | 434 +++++++++++++++++ .../visitor/PartsListVisitorStrategy.java | 256 ++++++++++ .../print/visitor/StageVisitorStrategy.java | 249 ++++++++++ .../gui/rocketfigure/BodyTubeShapes.java | 10 +- .../gui/rocketfigure/LaunchLugShapes.java | 10 +- .../gui/scalefigure/RocketFigure.java | 56 ++- .../gui/scalefigure/RocketPanel.java | 88 ++-- .../rocketcomponent/BodyComponent.java | 13 +- .../openrocket/rocketcomponent/BodyTube.java | 59 ++- .../rocketcomponent/CenteringRing.java | 10 + .../openrocket/rocketcomponent/Coaxial.java | 50 ++ .../rocketcomponent/ComponentVisitor.java | 169 +++++++ .../ComponentVisitorStrategy.java | 129 +++++ .../rocketcomponent/EllipticalFinSet.java | 11 + .../rocketcomponent/ExternalComponent.java | 10 + .../rocketcomponent/FreeformFinSet.java | 15 +- .../openrocket/rocketcomponent/InnerTube.java | 17 +- .../openrocket/rocketcomponent/LaunchLug.java | 28 +- .../openrocket/rocketcomponent/NoseCone.java | 10 + .../rocketcomponent/RadiusRingComponent.java | 13 +- .../rocketcomponent/RingComponent.java | 24 +- .../sf/openrocket/rocketcomponent/Rocket.java | 26 +- .../rocketcomponent/RocketComponent.java | 35 +- .../sf/openrocket/rocketcomponent/Stage.java | 41 +- .../rocketcomponent/Transition.java | 24 +- .../rocketcomponent/TrapezoidFinSet.java | 9 + .../openrocket/rocketcomponent/Visitable.java | 41 ++ .../openrocket/rocketcomponent/Visitor.java | 39 ++ src/net/sf/openrocket/util/TestRockets.java | 18 +- .../file/rocksim/LaunchLugHandlerTest.java | 6 +- 66 files changed, 5648 insertions(+), 278 deletions(-) create mode 100644 src/net/sf/openrocket/gui/components/ColorChooser.java create mode 100644 src/net/sf/openrocket/gui/dialogs/PrintDialog.java create mode 100644 src/net/sf/openrocket/gui/dialogs/PrintPanel.java create mode 100644 src/net/sf/openrocket/gui/print/DesignReport.java create mode 100644 src/net/sf/openrocket/gui/print/ITextHelper.java create mode 100644 src/net/sf/openrocket/gui/print/OpenRocketPrintable.java create mode 100644 src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java create mode 100644 src/net/sf/openrocket/gui/print/PaperSize.java create mode 100644 src/net/sf/openrocket/gui/print/PrintController.java create mode 100644 src/net/sf/openrocket/gui/print/PrintFigure.java create mode 100644 src/net/sf/openrocket/gui/print/PrintSimulationWorker.java create mode 100644 src/net/sf/openrocket/gui/print/PrintUnit.java create mode 100644 src/net/sf/openrocket/gui/print/PrintUtilities.java create mode 100644 src/net/sf/openrocket/gui/print/PrintableContext.java create mode 100644 src/net/sf/openrocket/gui/print/PrintableFinSet.java create mode 100644 src/net/sf/openrocket/gui/print/TemplateProperties.java create mode 100644 src/net/sf/openrocket/gui/print/components/CheckBoxNode.java create mode 100644 src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java create mode 100644 src/net/sf/openrocket/gui/print/components/CheckTreeManager.java create mode 100644 src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java create mode 100644 src/net/sf/openrocket/gui/print/components/RocketPrintTree.java create mode 100644 src/net/sf/openrocket/gui/print/visitor/BaseVisitorStrategy.java create mode 100644 src/net/sf/openrocket/gui/print/visitor/FinSetVisitorStrategy.java create mode 100644 src/net/sf/openrocket/gui/print/visitor/MotorMountVisitorStrategy.java create mode 100644 src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java create mode 100644 src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java create mode 100644 src/net/sf/openrocket/gui/print/visitor/StageVisitorStrategy.java create mode 100644 src/net/sf/openrocket/rocketcomponent/Coaxial.java create mode 100644 src/net/sf/openrocket/rocketcomponent/ComponentVisitor.java create mode 100644 src/net/sf/openrocket/rocketcomponent/ComponentVisitorStrategy.java create mode 100644 src/net/sf/openrocket/rocketcomponent/Visitable.java create mode 100644 src/net/sf/openrocket/rocketcomponent/Visitor.java diff --git a/.classpath b/.classpath index c71fbb9c..d1b79c76 100644 --- a/.classpath +++ b/.classpath @@ -18,5 +18,6 @@ + diff --git a/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java b/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java index 88cf91ff..cd7fea01 100644 --- a/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java +++ b/src/net/sf/openrocket/aerodynamics/barrowman/LaunchLugCalc.java @@ -16,10 +16,10 @@ public class LaunchLugCalc extends RocketComponentCalc { 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); } diff --git a/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java b/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java index 090ee3ea..24fddbbd 100644 --- a/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java +++ b/src/net/sf/openrocket/file/openrocket/OpenRocketLoader.java @@ -1,21 +1,12 @@ 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; @@ -35,7 +26,9 @@ import net.sf.openrocket.rocketcomponent.Clusterable; 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; @@ -52,6 +45,7 @@ import net.sf.openrocket.rocketcomponent.ReferenceType; 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; @@ -61,25 +55,30 @@ import net.sf.openrocket.rocketcomponent.ThicknessRingComponent; 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. @@ -244,7 +243,7 @@ class DocumentConfig { // 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))); @@ -329,7 +328,7 @@ class DocumentConfig { // 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( diff --git a/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java b/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java index 5caf2896..2ca8c88d 100644 --- a/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java +++ b/src/net/sf/openrocket/file/openrocket/savers/BodyTubeSaver.java @@ -25,7 +25,7 @@ public class BodyTubeSaver extends SymmetricComponentSaver { if (tube.isRadiusAutomatic()) elements.add("auto"); else - elements.add("" + tube.getRadius() + ""); + elements.add("" + tube.getOuterRadius() + ""); if (tube.isMotorMount()) { elements.addAll(motorMountParams(tube)); diff --git a/src/net/sf/openrocket/file/openrocket/savers/LaunchLugSaver.java b/src/net/sf/openrocket/file/openrocket/savers/LaunchLugSaver.java index 3008bcce..1598bc41 100644 --- a/src/net/sf/openrocket/file/openrocket/savers/LaunchLugSaver.java +++ b/src/net/sf/openrocket/file/openrocket/savers/LaunchLugSaver.java @@ -1,10 +1,10 @@ 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 { @@ -25,7 +25,7 @@ public class LaunchLugSaver extends ExternalComponentSaver { super.addParams(c, elements); LaunchLug lug = (LaunchLug) c; - elements.add("" + lug.getRadius() + ""); + elements.add("" + lug.getOuterRadius() + ""); elements.add("" + lug.getLength() + ""); elements.add("" + lug.getThickness() + ""); elements.add("" + (lug.getRadialDirection()*180.0/Math.PI) + ""); diff --git a/src/net/sf/openrocket/file/rocksim/BodyTubeHandler.java b/src/net/sf/openrocket/file/rocksim/BodyTubeHandler.java index a645827f..f8bc4838 100644 --- a/src/net/sf/openrocket/file/rocksim/BodyTubeHandler.java +++ b/src/net/sf/openrocket/file/rocksim/BodyTubeHandler.java @@ -54,7 +54,7 @@ class BodyTubeHandler extends BaseHandler { 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; diff --git a/src/net/sf/openrocket/file/rocksim/LaunchLugHandler.java b/src/net/sf/openrocket/file/rocksim/LaunchLugHandler.java index 049feedd..6d981ae4 100644 --- a/src/net/sf/openrocket/file/rocksim/LaunchLugHandler.java +++ b/src/net/sf/openrocket/file/rocksim/LaunchLugHandler.java @@ -53,7 +53,7 @@ class LaunchLugHandler extends PositionDependentHandler { 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)); diff --git a/src/net/sf/openrocket/file/rocksim/ParachuteHandler.java b/src/net/sf/openrocket/file/rocksim/ParachuteHandler.java index 44418fae..6d739eb0 100644 --- a/src/net/sf/openrocket/file/rocksim/ParachuteHandler.java +++ b/src/net/sf/openrocket/file/rocksim/ParachuteHandler.java @@ -68,7 +68,7 @@ class ParachuteHandler extends RecoveryDeviceHandler { 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; diff --git a/src/net/sf/openrocket/gui/components/ColorChooser.java b/src/net/sf/openrocket/gui/components/ColorChooser.java new file mode 100644 index 00000000..e149e3ca --- /dev/null +++ b/src/net/sf/openrocket/gui/components/ColorChooser.java @@ -0,0 +1,104 @@ +/* + * 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 diff --git a/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java b/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java index 874111a2..ebc490ac 100644 --- a/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/BodyTubeConfig.java @@ -1,11 +1,6 @@ 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; @@ -17,6 +12,11 @@ import net.sf.openrocket.rocketcomponent.BodyTube; 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; @@ -42,7 +42,7 @@ public class BodyTubeConfig extends RocketComponentConfig { //// 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()); diff --git a/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java b/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java index 909c5d31..893917a6 100644 --- a/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java +++ b/src/net/sf/openrocket/gui/configdialog/LaunchLugConfig.java @@ -1,11 +1,6 @@ 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; @@ -16,6 +11,11 @@ import net.sf.openrocket.material.Material; 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; @@ -44,7 +44,7 @@ public class LaunchLugConfig extends RocketComponentConfig { //// 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()); diff --git a/src/net/sf/openrocket/gui/dialogs/PrintDialog.java b/src/net/sf/openrocket/gui/dialogs/PrintDialog.java new file mode 100644 index 00000000..a9769eed --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/PrintDialog.java @@ -0,0 +1,270 @@ +/* + * 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 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); + } + } + } + } + +} diff --git a/src/net/sf/openrocket/gui/dialogs/PrintPanel.java b/src/net/sf/openrocket/gui/dialogs/PrintPanel.java new file mode 100644 index 00000000..ecd1fd8f --- /dev/null +++ b/src/net/sf/openrocket/gui/dialogs/PrintPanel.java @@ -0,0 +1,434 @@ +/* + * 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 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 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); + } + +} diff --git a/src/net/sf/openrocket/gui/figureelements/RocketInfo.java b/src/net/sf/openrocket/gui/figureelements/RocketInfo.java index f573e182..0e9ef165 100644 --- a/src/net/sf/openrocket/gui/figureelements/RocketInfo.java +++ b/src/net/sf/openrocket/gui/figureelements/RocketInfo.java @@ -1,6 +1,13 @@ 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; @@ -9,13 +16,8 @@ import java.awt.Rectangle; 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; /** @@ -174,11 +176,11 @@ public class RocketInfo implements FigureElement { } 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: "); @@ -226,8 +228,84 @@ public class RocketInfo implements FigureElement { } - - 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; diff --git a/src/net/sf/openrocket/gui/main/BasicFrame.java b/src/net/sf/openrocket/gui/main/BasicFrame.java index aba53986..43c214c7 100644 --- a/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -1,62 +1,5 @@ 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.lang.reflect.InvocationTargetException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLDecoder; -import java.util.ArrayList; -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.Timer; -import javax.swing.ToolTipManager; -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.communication.UpdateInfo; @@ -76,6 +19,7 @@ import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog; 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.UpdateInfoDialog; import net.sf.openrocket.gui.dialogs.WarningDialog; @@ -97,6 +41,62 @@ import net.sf.openrocket.util.Reflection; 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.Timer; +import javax.swing.ToolTipManager; +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.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; + public class BasicFrame extends JFrame { private static final LogHelper log = Application.getLogger(); @@ -480,7 +480,18 @@ public class BasicFrame extends JFrame { 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"); @@ -1113,7 +1124,14 @@ public class BasicFrame extends JFrame { closeAction(); } } - + + /** + * + */ + public void printAction() { + new PrintDialog(document); + } + /** * Open a new design window with a basic rocket+stage. */ diff --git a/src/net/sf/openrocket/gui/print/DesignReport.java b/src/net/sf/openrocket/gui/print/DesignReport.java new file mode 100644 index 00000000..bdd83097 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/DesignReport.java @@ -0,0 +1,457 @@ +/* + * DrawingsPrintable.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; + +/** + *
+ * #  Title # Section describing the rocket in general without motors
+ * # Section describing the rocket in general without motors
+ * 

+ * 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 + *

+ * # Section for each motor configuration + *

+ * 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 + *

+ *

+ */ +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 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 + */ + private void addMotorData (List motors, final PdfPTable parent, List weight) { + + PdfPTable motorTable = new PdfPTable(7); + motorTable.setWidthPercentage(68); + motorTable.setHorizontalAlignment(Element.ALIGN_LEFT); + + final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM); + final int mPad = 12; + 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 Weight", PdfPCell.BOTTOM)); + motorTable.addCell(ITextHelper.createCell("Dimensions", 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); + 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(weight, i); + motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", 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.copy(); + 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 getStageWeights (Rocket rocket) { + rocket.accept(new ComponentVisitor(svs)); + svs.close(); + List stages = svs.getStages(); + //TODO: Add in motor mass + 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 weights, int stage) { + + double result = 0d; + for (int i = 0; i <= stage; i++) { + result += weights.get(i); + } + return result; + } +} diff --git a/src/net/sf/openrocket/gui/print/ITextHelper.java b/src/net/sf/openrocket/gui/print/ITextHelper.java new file mode 100644 index 00000000..890c76e7 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/ITextHelper.java @@ -0,0 +1,88 @@ +/* + * ITextHelper.java + */ +package net.sf.openrocket.gui.print; + +import com.itextpdf.text.Chunk; +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.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; + +/** + * A bunch of helper methods for creating iText components. + */ +public final class ITextHelper { + + public static PdfPCell createCell () { + return createCell(Rectangle.BOTTOM); + } + + public static PdfPCell createCell (int border) { + PdfPCell result = new PdfPCell(); + result.setBorder(border); + + return result; + } + + public static PdfPCell createCell (PdfPTable table) { + PdfPCell result = new PdfPCell(); + result.setBorder(PdfPCell.NO_BORDER); + result.addElement(table); + + return result; + } + + public static PdfPCell createCell (String v) { + return createCell(v, Rectangle.NO_BORDER); + } + + public static PdfPCell createCell (String v, Font font) { + return createCell(v, font); + } + + public static PdfPCell createCell (String v, int leftPad, int rightPad) { + PdfPCell c = createCell(v, Rectangle.NO_BORDER); + c.setPaddingLeft(leftPad); + c.setPaddingRight(rightPad); + return c; + } + + public static PdfPCell createCell (String v, int border) { + PdfPCell result = new PdfPCell(); + result.setBorder(border); + Chunk c = new Chunk(); + c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE)); + c.append(v); + result.addElement(c); + return result; + } + + 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; + } + + public static Phrase createPhrase (String text) { + return createPhrase(text, PrintUtilities.NORMAL); + } + + 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; + } + + public static Paragraph createParagraph (String text) { + return createParagraph(text, PrintUtilities.NORMAL); + } +} \ No newline at end of file diff --git a/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java b/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java new file mode 100644 index 00000000..58d5ad3a --- /dev/null +++ b/src/net/sf/openrocket/gui/print/OpenRocketPrintable.java @@ -0,0 +1,87 @@ +/* + * 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; + } +} diff --git a/src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java b/src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java new file mode 100644 index 00000000..4b6a56b9 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PDFPrintStreamDoc.java @@ -0,0 +1,57 @@ +/* + * 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; + } +} diff --git a/src/net/sf/openrocket/gui/print/PaperSize.java b/src/net/sf/openrocket/gui/print/PaperSize.java new file mode 100644 index 00000000..e40d6f1c --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PaperSize.java @@ -0,0 +1,128 @@ +/* + * 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 paperNames = new HashMap(); + private static Map paperItext = new HashMap(); + + 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))); + } + +} diff --git a/src/net/sf/openrocket/gui/print/PrintController.java b/src/net/sf/openrocket/gui/print/PrintController.java new file mode 100644 index 00000000..41d4ffa1 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintController.java @@ -0,0 +1,121 @@ +/* + * 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 toBePrinted, OutputStream outputFile, MediaSizeName msn) { + + Document idoc = new Document(convertWithDefault(msn)); + PdfWriter writer = null; +// OutputStream fileOutputStream = null; + try { +// fileOutputStream = outputFile; + 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 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; + } + +} diff --git a/src/net/sf/openrocket/gui/print/PrintFigure.java b/src/net/sf/openrocket/gui/print/PrintFigure.java new file mode 100644 index 00000000..ae909269 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintFigure.java @@ -0,0 +1,33 @@ +/* + * 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(); + } +} diff --git a/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java b/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java new file mode 100644 index 00000000..b0b31b35 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintSimulationWorker.java @@ -0,0 +1,49 @@ +/* + * 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 diff --git a/src/net/sf/openrocket/gui/print/PrintUnit.java b/src/net/sf/openrocket/gui/print/PrintUnit.java new file mode 100644 index 00000000..c603f2f9 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintUnit.java @@ -0,0 +1,158 @@ +/* + * 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. + * + *

For example, to convert 10 inches to point, use: + * PrintUnit.POINTS.convert(10L, PrintUnit.INCHES) + * + * @param sourceLength the length in the given sourceUnit + * @param sourceUnit the unit of the sourceDuration argument + * + * @return the converted length in this unit, + * or Long.MIN_VALUE if conversion would negatively + * overflow, or Long.MAX_VALUE if it would positively overflow. + */ + public double convert(double sourceLength, PrintUnit sourceUnit) { + throw new AbstractMethodError(); + } + + /** + * Equivalent to INCHES.convert(length, this). + * + * @param length the length + * + * @return the converted length, + * or Long.MIN_VALUE if conversion would negatively + * overflow, or Long.MAX_VALUE if it would positively overflow. + * @see #convert + */ + public double toInches(double length) { + throw new AbstractMethodError(); + } + + /** + * Equivalent to MILLIMETERS.convert(length, this). + * + * @param length the length + * + * @return the converted length, + * or Long.MIN_VALUE if conversion would negatively + * overflow, or Long.MAX_VALUE if it would positively overflow. + * @see #convert + */ + public double toMillis(double length) { + throw new AbstractMethodError(); + } + + /** + * Equivalent to CENTIMETERS.convert(length, this). + * + * @param length the length + * + * @return the converted length, + * or Long.MIN_VALUE if conversion would negatively + * overflow, or Long.MAX_VALUE if it would positively overflow. + * @see #convert + */ + public double toCentis(double length) { + throw new AbstractMethodError(); + } + + /** + * Equivalent to METERS.convert(length, this). + * + * @param length the length + * + * @return the converted length, + * or Long.MIN_VALUE if conversion would negatively + * overflow, or Long.MAX_VALUE if it would positively overflow. + * @see #convert + */ + public double toMeters(double length) { + throw new AbstractMethodError(); + } + + /** + * Equivalent to POINTS.convert(length, this). + * + * @param length the length + * + * @return the converted length, + * or Long.MIN_VALUE if conversion would negatively + * overflow, or Long.MAX_VALUE if it would positively overflow. + * @see #convert + */ + public long toPoints(double length) { + throw new AbstractMethodError(); + } + +} \ No newline at end of file diff --git a/src/net/sf/openrocket/gui/print/PrintUtilities.java b/src/net/sf/openrocket/gui/print/PrintUtilities.java new file mode 100644 index 00000000..b2bc5c53 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintUtilities.java @@ -0,0 +1,142 @@ +/* + * 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(); + } + } +} diff --git a/src/net/sf/openrocket/gui/print/PrintableContext.java b/src/net/sf/openrocket/gui/print/PrintableContext.java new file mode 100644 index 00000000..6645b696 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintableContext.java @@ -0,0 +1,110 @@ +/* + * 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, Iterable { + + /** + * The stage number. May be null for printables that have no stage meaning. + */ + private Set stageNumber; + + /** + * The type of thing to be printed. + */ + private OpenRocketPrintable printable; + + private final Map> previous = new TreeMap>(); + + + 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 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 stages = previous.get(thePrintable); + if (stages == null) { + stages = new TreeSet(); + previous.put(thePrintable, stages); + } + if (theStageNumber != null) { + stages.add(theStageNumber); + } + } + + + public Iterator iterator () { + return new Iterator() { + + Iterator 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 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(); + } + +} diff --git a/src/net/sf/openrocket/gui/print/PrintableFinSet.java b/src/net/sf/openrocket/gui/print/PrintableFinSet.java new file mode 100644 index 00000000..6f82423c --- /dev/null +++ b/src/net/sf/openrocket/gui/print/PrintableFinSet.java @@ -0,0 +1,158 @@ +/* + * 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 actual fin set being printed. + */ + private FinSet finset; + + /** + * The object that represents the shape (outline) of the fin. This gets drawn onto the Swing component. + */ + private GeneralPath polygon = null; + + /** + * The margin. + */ + private final int margin = 20; + + /** + * Constructor. + * + * @param fs the finset to print + */ + public PrintableFinSet (FinSet fs) { + super(false); + finset = fs; + init(); + setBackground(Color.white); + } + + /** + * Initialize the fin set polygon and set the size of the component. + */ + private void init () { + Coordinate[] points = finset.getFinPointsWithTab(); + + 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(); + + setSize(maxX-minX+ margin, maxY-minY+ margin); + } + + /** + * From the java.awt.print.Printable interface. + * + * Prints the page at the specified index into the specified {@link java.awt.Graphics} context in the specified + * format. A PrinterJob calls the Printable interface to request that a page be rendered + * into the context specified by graphics. The format of the page to be drawn is specified by + * pageFormat. The zero based index of the requested page is specified by pageIndex. If + * the requested page does not exist then this method returns NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned. The + * Graphics class or subclass implements the {@link java.awt.print.PrinterGraphics} interface to + * provide additional information. If the Printable object aborts the print job then it throws a + * {@link java.awt.print.PrinterException}. + * + * @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 pageIndex 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(); + int height = getHeight(); + // 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(margin +5, margin +15); + g2d.setPaint(TemplateProperties.getFillColor()); + g2d.fill(polygon); + g2d.setPaint(TemplateProperties.getLineColor()); + g2d.draw(polygon); + } + +} diff --git a/src/net/sf/openrocket/gui/print/TemplateProperties.java b/src/net/sf/openrocket/gui/print/TemplateProperties.java new file mode 100644 index 00000000..f98f7ea3 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/TemplateProperties.java @@ -0,0 +1,49 @@ +/* + * 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; + } +} diff --git a/src/net/sf/openrocket/gui/print/components/CheckBoxNode.java b/src/net/sf/openrocket/gui/print/components/CheckBoxNode.java new file mode 100644 index 00000000..7fbab0a6 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/components/CheckBoxNode.java @@ -0,0 +1,77 @@ +/* + * 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 diff --git a/src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java b/src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java new file mode 100644 index 00000000..e4a93373 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/components/CheckTreeCellRenderer.java @@ -0,0 +1,83 @@ +/* + * 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. + *

+ * 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 diff --git a/src/net/sf/openrocket/gui/print/components/CheckTreeManager.java b/src/net/sf/openrocket/gui/print/components/CheckTreeManager.java new file mode 100644 index 00000000..719e50d7 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/components/CheckTreeManager.java @@ -0,0 +1,99 @@ +/* + * 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 diff --git a/src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java b/src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java new file mode 100644 index 00000000..9f34b4b5 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/components/CheckTreeSelectionModel.java @@ -0,0 +1,245 @@ +/* + * 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 toBeRemoved = new ArrayList(); + 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 stack = new Stack(); + 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 diff --git a/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java b/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java new file mode 100644 index 00000000..b250abef --- /dev/null +++ b/src/net/sf/openrocket/gui/print/components/RocketPrintTree.java @@ -0,0 +1,252 @@ +/* + * 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. + *

+ * 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 nodes = new ArrayList(); + 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 + */ + public Iterator 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 { + 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 diff --git a/src/net/sf/openrocket/gui/print/visitor/BaseVisitorStrategy.java b/src/net/sf/openrocket/gui/print/visitor/BaseVisitorStrategy.java new file mode 100644 index 00000000..1f7852b5 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/visitor/BaseVisitorStrategy.java @@ -0,0 +1,333 @@ +/* + * 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.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 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()); + } + + /** + * 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 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 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 diff --git a/src/net/sf/openrocket/gui/print/visitor/FinSetVisitorStrategy.java b/src/net/sf/openrocket/gui/print/visitor/FinSetVisitorStrategy.java new file mode 100644 index 00000000..8711039a --- /dev/null +++ b/src/net/sf/openrocket/gui/print/visitor/FinSetVisitorStrategy.java @@ -0,0 +1,76 @@ +/* + * FinSetVisitorStrategy.java + */ +package net.sf.openrocket.gui.print.visitor; + +import com.itextpdf.text.Document; +import com.itextpdf.text.pdf.PdfContentByte; +import com.itextpdf.text.pdf.PdfWriter; +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.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 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())) { + PrintableFinSet pfs = new PrintableFinSet(visitable); + + net.sf.openrocket.gui.print.visitor.Dimension d = getPageSize(); + PdfContentByte cb = writer.getDirectContent(); + Graphics2D g2 = cb.createGraphics(d.width, d.height); + pfs.print(g2); + g2.dispose(); + document.newPage(); + } + } +} + diff --git a/src/net/sf/openrocket/gui/print/visitor/MotorMountVisitorStrategy.java b/src/net/sf/openrocket/gui/print/visitor/MotorMountVisitorStrategy.java new file mode 100644 index 00000000..579cb2da --- /dev/null +++ b/src/net/sf/openrocket/gui/print/visitor/MotorMountVisitorStrategy.java @@ -0,0 +1,100 @@ +/* + * 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 motors = new ArrayList(); + + /** + * 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 getMotors () { + return motors; + } + +} + + diff --git a/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java b/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java new file mode 100644 index 00000000..274f8d31 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/visitor/PartsDetailVisitorStrategy.java @@ -0,0 +1,434 @@ +/* + * 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.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 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 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 x = visitable.getComponentBounds(); + + try { + img = Image.getInstance(writer, awtImage, 0.25f); + } + catch (BadElementException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + catch (IOException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + + 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()) + ")"; + } + +} diff --git a/src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java b/src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java new file mode 100644 index 00000000..f6ebab8f --- /dev/null +++ b/src/net/sf/openrocket/gui/print/visitor/PartsListVisitorStrategy.java @@ -0,0 +1,256 @@ +/* + * 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 crap = new HashMap(); + + /** + * 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 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 diff --git a/src/net/sf/openrocket/gui/print/visitor/StageVisitorStrategy.java b/src/net/sf/openrocket/gui/print/visitor/StageVisitorStrategy.java new file mode 100644 index 00000000..19e81e73 --- /dev/null +++ b/src/net/sf/openrocket/gui/print/visitor/StageVisitorStrategy.java @@ -0,0 +1,249 @@ +/* + * 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.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 stageComponents = new ArrayList(); + + 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 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 getStages () { + return stageComponents; + } + + /** + * Close by setting the last stage. + */ + public void close () { + if (mass > 0d) { + stageComponents.add(mass); + } + } + +} diff --git a/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java b/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java index 9cef22d1..aa299490 100644 --- a/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java +++ b/src/net/sf/openrocket/gui/rocketfigure/BodyTubeShapes.java @@ -1,12 +1,12 @@ 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 { @@ -15,7 +15,7 @@ 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]; @@ -31,7 +31,7 @@ public class BodyTubeShapes extends RocketComponentShapes { 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))); diff --git a/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java b/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java index 7dfb1b7a..f7084ee6 100644 --- a/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java +++ b/src/net/sf/openrocket/gui/rocketfigure/LaunchLugShapes.java @@ -1,12 +1,12 @@ 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 { @@ -15,7 +15,7 @@ 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]; @@ -31,7 +31,7 @@ public class LaunchLugShapes extends RocketComponentShapes { 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))); diff --git a/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java index c19b5e8f..ea1f98c7 100644 --- a/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java +++ b/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java @@ -1,6 +1,20 @@ 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; @@ -19,20 +33,6 @@ import java.util.Collection; 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 ScaleFigure that draws a complete rocket. Extra information can * be added to the figure by the methods {@link #addRelativeExtra(FigureElement)}, @@ -256,14 +256,10 @@ public class RocketFigure extends AbstractScaleFigure { 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; @@ -394,9 +390,19 @@ public class RocketFigure extends AbstractScaleFigure { } } - - - 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 { diff --git a/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index 6a8e68d0..c75fa072 100644 --- a/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -1,35 +1,6 @@ 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; @@ -67,6 +38,34 @@ import net.sf.openrocket.util.Coordinate; 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. * @@ -260,8 +259,35 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change 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); } diff --git a/src/net/sf/openrocket/rocketcomponent/BodyComponent.java b/src/net/sf/openrocket/rocketcomponent/BodyComponent.java index f17bd3d9..9eb970c6 100644 --- a/src/net/sf/openrocket/rocketcomponent/BodyComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/BodyComponent.java @@ -23,7 +23,6 @@ public abstract class BodyComponent extends ExternalComponent { } - /** * Get the outer radius of the component at cylindrical coordinate (x,theta). * @@ -76,4 +75,16 @@ public abstract class BodyComponent extends ExternalComponent { return true; return false; } + + /** + * 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); + } + + } diff --git a/src/net/sf/openrocket/rocketcomponent/BodyTube.java b/src/net/sf/openrocket/rocketcomponent/BodyTube.java index 2e272f95..fd5eb4a4 100644 --- a/src/net/sf/openrocket/rocketcomponent/BodyTube.java +++ b/src/net/sf/openrocket/rocketcomponent/BodyTube.java @@ -1,13 +1,13 @@ 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. @@ -15,7 +15,7 @@ import net.sf.openrocket.util.MathUtil; * @author Sampo Niskanen */ -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 @@ -61,8 +61,11 @@ public class BodyTube extends SymmetricComponent implements MotorMount { /** * 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; @@ -88,8 +91,11 @@ public class BodyTube extends SymmetricComponent implements MotorMount { * 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; @@ -123,9 +129,9 @@ public class BodyTube extends SymmetricComponent implements MotorMount { @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(); } @Override @@ -144,7 +150,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount { return -1; } } - return getRadius(); + return getOuterRadius(); } @Override @@ -158,7 +164,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount { return -1; } } - return getRadius(); + return getOuterRadius(); } @@ -166,15 +172,16 @@ public class BodyTube extends SymmetricComponent implements MotorMount { - + @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); } @@ -188,16 +195,26 @@ public class BodyTube extends SymmetricComponent implements MotorMount { 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(); } /** @@ -208,7 +225,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount { if (filled) return 0.0; else - return Math.max(getRadius()-thickness,0); + return Math.max(getOuterRadius()-thickness,0); } @@ -225,7 +242,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount { */ @Override public double getComponentVolume() { - double r = getRadius(); + double r = getOuterRadius(); if (filled) return getFilledVolume(r,length); else @@ -236,14 +253,14 @@ public class BodyTube extends SymmetricComponent implements MotorMount { @Override public double getLongitudalUnitInertia() { // 1/12 * (3 * (r1^2 + r2^2) + h^2) - return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getRadius()) + + 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; } @@ -266,7 +283,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount { @Override public Collection getComponentBounds() { Collection bounds = new ArrayList(8); - double r = getRadius(); + double r = getOuterRadius(); addBound(bounds,0,r); addBound(bounds,length,r); return bounds; diff --git a/src/net/sf/openrocket/rocketcomponent/CenteringRing.java b/src/net/sf/openrocket/rocketcomponent/CenteringRing.java index b0f2aa93..1621042c 100644 --- a/src/net/sf/openrocket/rocketcomponent/CenteringRing.java +++ b/src/net/sf/openrocket/rocketcomponent/CenteringRing.java @@ -61,5 +61,15 @@ public class CenteringRing extends RadiusRingComponent { public boolean isCompatible(Class 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); + } } diff --git a/src/net/sf/openrocket/rocketcomponent/Coaxial.java b/src/net/sf/openrocket/rocketcomponent/Coaxial.java new file mode 100644 index 00000000..ad2338fc --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Coaxial.java @@ -0,0 +1,50 @@ +/* + * 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(); + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ComponentVisitor.java b/src/net/sf/openrocket/rocketcomponent/ComponentVisitor.java new file mode 100644 index 00000000..636f5f07 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ComponentVisitor.java @@ -0,0 +1,169 @@ +/* + * 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 { + + /** + * 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 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(); + } + +} diff --git a/src/net/sf/openrocket/rocketcomponent/ComponentVisitorStrategy.java b/src/net/sf/openrocket/rocketcomponent/ComponentVisitorStrategy.java new file mode 100644 index 00000000..59567230 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/ComponentVisitorStrategy.java @@ -0,0 +1,129 @@ +/* + * 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 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(); +} diff --git a/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java b/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java index d4ecac83..22d49bea 100644 --- a/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/EllipticalFinSet.java @@ -67,4 +67,15 @@ public class EllipticalFinSet extends FinSet { 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); + } + } diff --git a/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java b/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java index 8e11c914..8a120904 100644 --- a/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/ExternalComponent.java @@ -126,4 +126,14 @@ public abstract class ExternalComponent extends RocketComponent { 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); + } + } diff --git a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java index 19f9e0ad..1b80e426 100644 --- a/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/FreeformFinSet.java @@ -1,11 +1,11 @@ package net.sf.openrocket.rocketcomponent; -import java.util.ArrayList; -import java.util.Arrays; - 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 { @@ -305,6 +305,15 @@ public class FreeformFinSet extends FinSet { 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 points) throws IllegalFinPointException { final int n = points.size(); diff --git a/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/src/net/sf/openrocket/rocketcomponent/InnerTube.java index 8ef7d1f7..0a0b6327 100644 --- a/src/net/sf/openrocket/rocketcomponent/InnerTube.java +++ b/src/net/sf/openrocket/rocketcomponent/InnerTube.java @@ -1,13 +1,13 @@ 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.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 @@ -307,6 +307,15 @@ implements Clusterable, RadialParent, MotorMount { } + /** + * 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); + } /* diff --git a/src/net/sf/openrocket/rocketcomponent/LaunchLug.java b/src/net/sf/openrocket/rocketcomponent/LaunchLug.java index 9fbfcae4..2670bc60 100644 --- a/src/net/sf/openrocket/rocketcomponent/LaunchLug.java +++ b/src/net/sf/openrocket/rocketcomponent/LaunchLug.java @@ -1,13 +1,13 @@ package net.sf.openrocket.rocketcomponent; -import java.util.ArrayList; -import java.util.Collection; - import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; +import java.util.ArrayList; +import java.util.Collection; + -public class LaunchLug extends ExternalComponent { +public class LaunchLug extends ExternalComponent implements Coaxial { private double radius; private double thickness; @@ -27,11 +27,11 @@ public class LaunchLug extends ExternalComponent { } - 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; @@ -44,7 +44,7 @@ public class LaunchLug extends ExternalComponent { } public void setInnerRadius(double innerRadius) { - setRadius(innerRadius + thickness); + setOuterRadius(innerRadius + thickness); } public double getThickness() { @@ -175,14 +175,14 @@ public class LaunchLug extends ExternalComponent { @Override public double getLongitudalUnitInertia() { // 1/12 * (3 * (r1^2 + r2^2) + h^2) - return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getRadius()) + + 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; } @@ -192,4 +192,14 @@ public class LaunchLug extends ExternalComponent { 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); + } + } diff --git a/src/net/sf/openrocket/rocketcomponent/NoseCone.java b/src/net/sf/openrocket/rocketcomponent/NoseCone.java index 04eaa77c..1aa61a4c 100644 --- a/src/net/sf/openrocket/rocketcomponent/NoseCone.java +++ b/src/net/sf/openrocket/rocketcomponent/NoseCone.java @@ -114,4 +114,14 @@ public class NoseCone extends Transition { 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); + } } diff --git a/src/net/sf/openrocket/rocketcomponent/RadiusRingComponent.java b/src/net/sf/openrocket/rocketcomponent/RadiusRingComponent.java index f7b7be84..b3d86251 100644 --- a/src/net/sf/openrocket/rocketcomponent/RadiusRingComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/RadiusRingComponent.java @@ -11,7 +11,7 @@ import net.sf.openrocket.util.MathUtil; * * @author Sampo Niskanen */ -public abstract class RadiusRingComponent extends RingComponent { +public abstract class RadiusRingComponent extends RingComponent implements Coaxial { protected double outerRadius = 0; protected double innerRadius = 0; @@ -80,4 +80,15 @@ public abstract class RadiusRingComponent extends RingComponent { 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); + } + } diff --git a/src/net/sf/openrocket/rocketcomponent/RingComponent.java b/src/net/sf/openrocket/rocketcomponent/RingComponent.java index 3d67940b..0450b415 100644 --- a/src/net/sf/openrocket/rocketcomponent/RingComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/RingComponent.java @@ -1,12 +1,12 @@ 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 @@ -16,7 +16,7 @@ import net.sf.openrocket.util.MathUtil; * * @author Sampo Niskanen */ -public abstract class RingComponent extends StructuralComponent { +public abstract class RingComponent extends StructuralComponent implements Coaxial { protected boolean outerRadiusAutomatic = false; protected boolean innerRadiusAutomatic = false; @@ -30,13 +30,17 @@ public abstract class RingComponent extends StructuralComponent { - + @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); @@ -209,4 +213,14 @@ public abstract class RingComponent extends StructuralComponent { 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); + } + } diff --git a/src/net/sf/openrocket/rocketcomponent/Rocket.java b/src/net/sf/openrocket/rocketcomponent/Rocket.java index 9ab110b4..a0c923ed 100644 --- a/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -1,5 +1,12 @@ package net.sf.openrocket.rocketcomponent; +import net.sf.openrocket.motor.Motor; +import net.sf.openrocket.util.Chars; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.MathUtil; + +import javax.swing.event.ChangeListener; +import javax.swing.event.EventListenerList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -9,14 +16,6 @@ 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.motor.Motor; -import net.sf.openrocket.util.Chars; -import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.MathUtil; - /** * Base for all rocket components. This is the "starting point" for all rocket trees. @@ -135,7 +134,6 @@ public class Rocket extends RocketComponent { } - /** * 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 @@ -795,4 +793,14 @@ public class Rocket extends RocketComponent { public boolean isCompatible(Class 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); + } } diff --git a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index a945d7b2..71d2c139 100644 --- a/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -1,5 +1,12 @@ package net.sf.openrocket.rocketcomponent; +import net.sf.openrocket.util.BugException; +import net.sf.openrocket.util.ChangeSource; +import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.LineStyle; +import net.sf.openrocket.util.MathUtil; + +import javax.swing.event.ChangeListener; import java.awt.Color; import java.util.ArrayList; import java.util.Collection; @@ -11,17 +18,9 @@ import java.util.NoSuchElementException; import java.util.Stack; import java.util.UUID; -import javax.swing.event.ChangeListener; - -import net.sf.openrocket.util.BugException; -import net.sf.openrocket.util.ChangeSource; -import net.sf.openrocket.util.Coordinate; -import net.sf.openrocket.util.LineStyle; -import net.sf.openrocket.util.MathUtil; - public abstract class RocketComponent implements ChangeSource, Cloneable, - Iterable { + Iterable , Visitable { /* * Text is suitable to the form @@ -121,12 +120,8 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, this.relativePosition = relativePosition; this.id = UUID.randomUUID().toString(); } - - - - - - //////////// Methods that must be implemented //////////// + + //////////// Methods that must be implemented //////////// /** @@ -328,6 +323,16 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, } + /** + * 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 //////////// diff --git a/src/net/sf/openrocket/rocketcomponent/Stage.java b/src/net/sf/openrocket/rocketcomponent/Stage.java index 74a36a5e..ed2fc7a9 100644 --- a/src/net/sf/openrocket/rocketcomponent/Stage.java +++ b/src/net/sf/openrocket/rocketcomponent/Stage.java @@ -2,22 +2,31 @@ package net.sf.openrocket.rocketcomponent; public class Stage extends ComponentAssembly { - @Override - public String getComponentName() { - return "Stage"; - } + @Override + public String getComponentName () { + return "Stage"; + } - - /** - * 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 type) { - return BodyComponent.class.isAssignableFrom(type); - } + /** + * 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 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); + } } diff --git a/src/net/sf/openrocket/rocketcomponent/Transition.java b/src/net/sf/openrocket/rocketcomponent/Transition.java index f598ad1e..f26666ae 100644 --- a/src/net/sf/openrocket/rocketcomponent/Transition.java +++ b/src/net/sf/openrocket/rocketcomponent/Transition.java @@ -1,13 +1,16 @@ 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 { @@ -354,7 +357,7 @@ 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; @@ -497,6 +500,15 @@ public class Transition extends SymmetricComponent { 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); + } /** diff --git a/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java b/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java index c92c65ea..6f8ef943 100644 --- a/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java +++ b/src/net/sf/openrocket/rocketcomponent/TrapezoidFinSet.java @@ -166,4 +166,13 @@ public class TrapezoidFinSet extends FinSet { 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); + } } diff --git a/src/net/sf/openrocket/rocketcomponent/Visitable.java b/src/net/sf/openrocket/rocketcomponent/Visitable.java new file mode 100644 index 00000000..cb516a5e --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Visitable.java @@ -0,0 +1,41 @@ +/* + * 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. visit 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. + * + * The visitor type + * The visitable (the concrete class that implements this interface) + */ +public interface Visitable, T extends Visitable> { + + /** + * 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); + +} diff --git a/src/net/sf/openrocket/rocketcomponent/Visitor.java b/src/net/sf/openrocket/rocketcomponent/Visitor.java new file mode 100644 index 00000000..2e689ea6 --- /dev/null +++ b/src/net/sf/openrocket/rocketcomponent/Visitor.java @@ -0,0 +1,39 @@ +/* + * 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. visit 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. + * + * The visitor type (the concrete class that implements this interface) + * The visitable + */ +public interface Visitor, T extends Visitable> { + + /** + * The callback method. This method is the 2nd leg of the double-dispatch, having been invoked from a + * corresponding accept. + * + * @param visitable the instance of the Visitable (the target of what is being visiting) + */ + void visit(T visitable); +} \ No newline at end of file diff --git a/src/net/sf/openrocket/util/TestRockets.java b/src/net/sf/openrocket/util/TestRockets.java index 1748fcd8..50d06547 100644 --- a/src/net/sf/openrocket/util/TestRockets.java +++ b/src/net/sf/openrocket/util/TestRockets.java @@ -1,8 +1,5 @@ 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; @@ -10,27 +7,30 @@ import net.sf.openrocket.rocketcomponent.BodyTube; 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; @@ -139,7 +139,7 @@ public class TestRockets { 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); diff --git a/test/net/sf/openrocket/file/rocksim/LaunchLugHandlerTest.java b/test/net/sf/openrocket/file/rocksim/LaunchLugHandlerTest.java index 689c86f2..9c6a48b9 100644 --- a/test/net/sf/openrocket/file/rocksim/LaunchLugHandlerTest.java +++ b/test/net/sf/openrocket/file/rocksim/LaunchLugHandlerTest.java @@ -107,11 +107,11 @@ public class LaunchLugHandlerTest extends RocksimTestBase { 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(); -- 2.30.2