2 * PartsDetailVisitorStrategy.java
4 package net.sf.openrocket.gui.print.visitor;
6 import com.itextpdf.text.*;
7 import com.itextpdf.text.pdf.PdfPCell;
8 import com.itextpdf.text.pdf.PdfPTable;
9 import com.itextpdf.text.pdf.PdfWriter;
10 import net.sf.openrocket.gui.main.ComponentIcons;
11 import net.sf.openrocket.gui.print.ITextHelper;
12 import net.sf.openrocket.gui.print.PrintUtilities;
13 import net.sf.openrocket.gui.print.PrintableFinSet;
14 import net.sf.openrocket.logging.LogHelper;
15 import net.sf.openrocket.material.Material;
16 import net.sf.openrocket.rocketcomponent.*;
17 import net.sf.openrocket.startup.Application;
18 import net.sf.openrocket.unit.Unit;
19 import net.sf.openrocket.unit.UnitGroup;
20 import net.sf.openrocket.util.Coordinate;
23 import java.text.NumberFormat;
24 import java.util.Collection;
25 import java.util.List;
29 * A visitor strategy for creating documentation about parts details.
31 public class PartsDetailVisitorStrategy {
36 private static final LogHelper log = Application.getLogger();
39 * The number of columns in the table.
41 private static final int TABLE_COLUMNS = 7;
44 * The parts detail is represented as an iText table.
51 protected Document document;
54 * The direct iText writer.
56 protected PdfWriter writer;
59 * The stages selected.
61 protected Set<Integer> stages;
64 * State variable to track the level of hierarchy.
66 protected int level = 0;
68 private static final String LINES = "Lines: ";
69 private static final String MASS = "Mass: ";
70 private static final String LEN = "Len: ";
71 private static final String THICK = "Thick: ";
72 private static final String INNER = "in ";
73 private static final String DIAMETER = "Dia";
74 private static final String OUTER = "out";
75 private static final String WIDTH = "Width";
76 private static final String LENGTH = "Length";
77 private static final String SHROUD_LINES = "Shroud Lines";
78 private static final String AFT_DIAMETER = "Aft Dia: ";
79 private static final String FORE_DIAMETER = "Fore Dia: ";
80 private static final String PARTS_DETAIL = "Parts Detail";
83 * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
85 * @param doc The iText document
86 * @param theWriter The direct iText writer
87 * @param theStagesToVisit The stages to be visited by this strategy
89 public PartsDetailVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
92 stages = theStagesToVisit;
93 PrintUtilities.addText(doc, PrintUtilities.BIG_BOLD, PARTS_DETAIL);
97 * Print the parts detail.
99 * @param root the root component
101 public void writeToDocument (final RocketComponent root) {
102 goDeep(root.getChildren());
106 * Recurse through the given rocket component.
108 * @param theRc an array of rocket components; all children will be visited recursively
110 protected void goDeep (final List<RocketComponent> theRc) {
112 for (RocketComponent rocketComponent : theRc) {
113 handle(rocketComponent);
119 * Add a line to the detail report based upon the type of the component.
121 * @param component the component to print the detail for
123 private void handle (RocketComponent component) {
124 //This ugly if-then-else construct is not object oriented. Originally it was an elegant, and very OO savy, design
125 //using the Visitor pattern. Unfortunately, it was misunderstood and was removed.
126 if (component instanceof Stage) {
131 document.add(ITextHelper.createPhrase(component.getName()));
132 grid = new PdfPTable(TABLE_COLUMNS);
133 grid.setWidthPercentage(100);
134 grid.setHorizontalAlignment(Element.ALIGN_LEFT);
136 catch (DocumentException e) {
139 List<RocketComponent> rc = component.getChildren();
142 else if (component instanceof LaunchLug) {
143 LaunchLug ll = (LaunchLug) component;
144 grid.addCell(iconToImage(component));
145 grid.addCell(createNameCell(component.getName(), true));
147 grid.addCell(createMaterialCell(ll.getMaterial()));
148 grid.addCell(createOuterInnerDiaCell(ll));
149 grid.addCell(createLengthCell(component.getLength()));
150 grid.addCell(createMassCell(component.getMass()));
152 else if (component instanceof NoseCone) {
153 NoseCone nc = (NoseCone) component;
154 grid.addCell(iconToImage(component));
155 grid.addCell(createNameCell(component.getName(), true));
156 grid.addCell(createMaterialCell(nc.getMaterial()));
157 grid.addCell(ITextHelper.createCell(nc.getType().getName(), PdfPCell.BOTTOM));
158 grid.addCell(createLengthCell(component.getLength()));
159 grid.addCell(createMassCell(component.getMass()));
160 List<RocketComponent> rc = component.getChildren();
163 else if (component instanceof Transition) {
164 Transition tran = (Transition) component;
165 grid.addCell(iconToImage(component));
166 grid.addCell(createNameCell(component.getName(), true));
167 grid.addCell(createMaterialCell(tran.getMaterial()));
169 Chunk fore = new Chunk(FORE_DIAMETER + toLength(tran.getForeRadius() * 2));
170 fore.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
171 Chunk aft = new Chunk(AFT_DIAMETER + toLength(tran.getAftRadius() * 2));
172 aft.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
173 final PdfPCell cell = ITextHelper.createCell();
174 cell.addElement(fore);
175 cell.addElement(aft);
177 grid.addCell(createLengthCell(component.getLength()));
178 grid.addCell(createMassCell(component.getMass()));
180 List<RocketComponent> rc = component.getChildren();
183 else if (component instanceof BodyTube) {
184 BodyTube bt = (BodyTube) component;
185 grid.addCell(iconToImage(component));
186 grid.addCell(createNameCell(component.getName(), true));
187 grid.addCell(createMaterialCell(bt.getMaterial()));
188 grid.addCell(createOuterInnerDiaCell(bt));
189 grid.addCell(createLengthCell(component.getLength()));
190 grid.addCell(createMassCell(component.getMass()));
191 List<RocketComponent> rc = component.getChildren();
194 else if (component instanceof FinSet) {
195 handleFins((FinSet) component);
197 else if (component instanceof BodyComponent) {
198 grid.addCell(component.getName());
200 List<RocketComponent> rc = component.getChildren();
203 else if (component instanceof ExternalComponent) {
204 ExternalComponent ext = (ExternalComponent) component;
205 grid.addCell(iconToImage(component));
206 grid.addCell(createNameCell(component.getName(), true));
208 grid.addCell(createMaterialCell(ext.getMaterial()));
209 grid.addCell(ITextHelper.createCell());
210 grid.addCell(createLengthCell(component.getLength()));
211 grid.addCell(createMassCell(component.getMass()));
213 List<RocketComponent> rc = component.getChildren();
216 else if (component instanceof InnerTube) {
217 InnerTube it = (InnerTube) component;
218 grid.addCell(iconToImage(component));
219 final PdfPCell pCell = createNameCell(component.getName(), true);
221 grid.addCell(createMaterialCell(it.getMaterial()));
222 grid.addCell(createOuterInnerDiaCell(it));
223 grid.addCell(createLengthCell(component.getLength()));
224 grid.addCell(createMassCell(component.getMass()));
226 List<RocketComponent> rc = component.getChildren();
229 else if (component instanceof RadiusRingComponent) {
230 RadiusRingComponent rrc = (RadiusRingComponent) component;
231 grid.addCell(iconToImage(component));
232 grid.addCell(createNameCell(component.getName(), true));
233 grid.addCell(createMaterialCell(rrc.getMaterial()));
234 if (component instanceof Bulkhead) {
235 grid.addCell(createDiaCell(rrc.getOuterRadius()*2));
238 grid.addCell(createOuterInnerDiaCell(rrc));
240 grid.addCell(createLengthCell(component.getLength()));
241 grid.addCell(createMassCell(component.getMass()));
242 List<RocketComponent> rc = component.getChildren();
245 else if (component instanceof RingComponent) {
246 RingComponent ring = (RingComponent) component;
247 grid.addCell(iconToImage(component));
248 grid.addCell(createNameCell(component.getName(), true));
249 grid.addCell(createMaterialCell(ring.getMaterial()));
250 grid.addCell(createOuterInnerDiaCell(ring));
251 grid.addCell(createLengthCell(component.getLength()));
252 grid.addCell(createMassCell(component.getMass()));
254 List<RocketComponent> rc = component.getChildren();
257 else if (component instanceof ShockCord) {
258 ShockCord ring = (ShockCord) component;
259 PdfPCell cell = ITextHelper.createCell();
260 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
261 cell.setPaddingBottom(12f);
262 grid.addCell(iconToImage(component));
263 grid.addCell(createNameCell(component.getName(), true));
264 grid.addCell(createMaterialCell(ring.getMaterial()));
266 grid.addCell(createLengthCell(ring.getCordLength()));
267 grid.addCell(createMassCell(component.getMass()));
269 else if (component instanceof Parachute) {
270 Parachute chute = (Parachute) component;
271 PdfPCell cell = ITextHelper.createCell();
272 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
273 cell.setPaddingBottom(12f);
274 grid.addCell(iconToImage(component));
275 grid.addCell(createNameCell(component.getName(), true));
276 grid.addCell(createMaterialCell(chute.getMaterial()));
277 grid.addCell(createDiaCell(chute.getDiameter()));
278 grid.addCell(createLengthCell(component.getLength()));
279 grid.addCell(createMassCell(component.getMass()));
281 grid.addCell(iconToImage(null));
282 grid.addCell(createNameCell(SHROUD_LINES, true));
283 grid.addCell(createMaterialCell(chute.getLineMaterial()));
284 grid.addCell(createLinesCell(chute.getLineCount()));
285 grid.addCell(createLengthCell(chute.getLineLength()));
288 else if (component instanceof Streamer) {
289 Streamer ring = (Streamer) component;
290 PdfPCell cell = ITextHelper.createCell();
291 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
292 cell.setPaddingBottom(12f);
293 grid.addCell(iconToImage(component));
294 grid.addCell(createNameCell(component.getName(), true));
295 grid.addCell(createMaterialCell(ring.getMaterial()));
296 grid.addCell(createStrip(ring));
297 grid.addCell(createLengthCell(component.getLength()));
298 grid.addCell(createMassCell(component.getMass()));
300 else if (component instanceof RecoveryDevice) {
301 RecoveryDevice device = (RecoveryDevice) component;
302 PdfPCell cell = ITextHelper.createCell();
303 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
304 cell.setPaddingBottom(12f);
305 grid.addCell(iconToImage(component));
306 grid.addCell(createNameCell(component.getName(), true));
307 grid.addCell(createMaterialCell(device.getMaterial()));
309 grid.addCell(createLengthCell(component.getLength()));
310 grid.addCell(createMassCell(component.getMass()));
312 else if (component instanceof MassObject) {
313 PdfPCell cell = ITextHelper.createCell();
314 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
315 cell.setPaddingBottom(12f);
317 grid.addCell(iconToImage(component));
318 final PdfPCell nameCell = createNameCell(component.getName(), true);
319 nameCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
320 nameCell.setPaddingBottom(12f);
321 grid.addCell(nameCell);
323 grid.addCell(createDiaCell(((MassObject) component).getRadius() * 2));
325 grid.addCell(createMassCell(component.getMass()));
330 * Close the strategy by adding the last grid to the document.
332 public void close () {
338 catch (DocumentException e) {
339 log.error("Could not write last cell to document.", e);
344 * Create a cell to document an outer 'diameter'. This is used for components that have no inner diameter, such as
345 * a solid parachute or bulkhead.
347 * @param diameter the diameter in default length units
349 * @return a formatted cell containing the diameter
351 private PdfPCell createDiaCell (final double diameter) {
352 PdfPCell result = new PdfPCell();
353 Phrase p = new Phrase();
355 result.setVerticalAlignment(Element.ALIGN_TOP);
356 result.setBorder(Rectangle.BOTTOM);
357 Chunk c = new Chunk();
358 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
363 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
368 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
369 c.append(" " + toLength(diameter));
371 result.addElement(p);
376 * Create a PDF cell for a streamer.
378 * @param component a component that is a Coaxial
379 * @return the PDF cell that has the streamer documented
381 private PdfPCell createStrip (final Streamer component) {
382 PdfPCell result = new PdfPCell();
383 Phrase p = new Phrase();
385 result.setVerticalAlignment(Element.ALIGN_TOP);
386 result.setBorder(Rectangle.BOTTOM);
387 Chunk c = new Chunk();
388 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
393 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
394 c.append(" " + toLength(component.getStripLength()));
396 result.addElement(p);
398 Phrase pw = new Phrase();
401 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
406 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
407 c.append(" " + toLength(component.getStripWidth()));
409 result.addElement(pw);
415 * Create a PDF cell that documents both an outer and an inner diameter of a component.
417 * @param component a component that is a Coaxial
418 * @return the PDF cell that has the outer and inner diameters documented
420 private PdfPCell createOuterInnerDiaCell (final Coaxial component) {
421 PdfPCell result = new PdfPCell();
422 Phrase p = new Phrase();
424 result.setVerticalAlignment(Element.ALIGN_TOP);
425 result.setBorder(Rectangle.BOTTOM);
426 Chunk c = new Chunk();
427 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
432 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
437 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
438 c.append(" " + toLength(component.getOuterRadius() * 2));
440 createInnerDiaCell(component, result);
441 result.addElement(p);
446 * Add inner diameter data to a cell.
448 * @param component a component that is a Coaxial
449 * @param cell the PDF cell to add the inner diameter data to
451 private void createInnerDiaCell (final Coaxial component, PdfPCell cell) {
452 Phrase p = new Phrase();
454 Chunk c = new Chunk();
455 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
460 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
465 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
466 c.append(" " + toLength(component.getInnerRadius() * 2));
472 * Add PDF cells for a fin set.
474 * @param theFinSet the fin set
476 private void handleFins (FinSet theFinSet) {
479 java.awt.Image awtImage = new PrintableFinSet(theFinSet).createImage();
481 Collection<Coordinate> x = theFinSet.getComponentBounds();
484 img = Image.getInstance(writer, awtImage, 0.25f);
486 catch (Exception e) {
487 log.error("Could not write image to document.", e);
490 grid.addCell(iconToImage(theFinSet));
491 grid.addCell(createNameCell(theFinSet.getName() + " (" + theFinSet.getFinCount() + ")", true));
492 grid.addCell(createMaterialCell(theFinSet.getMaterial()));
493 grid.addCell(ITextHelper.createCell(THICK + toLength(theFinSet.getThickness()), PdfPCell.BOTTOM));
494 final PdfPCell pCell = new PdfPCell();
495 pCell.setBorder(Rectangle.BOTTOM);
496 pCell.addElement(img);
498 grid.addCell(ITextHelper.createCell());
499 grid.addCell(createMassCell(theFinSet.getMass()));
501 List<RocketComponent> rc = theFinSet.getChildren();
506 * Create a length formatted cell.
508 * @param length the length, in default length units
510 * @return a PdfPCell that is formatted with the length
512 protected PdfPCell createLengthCell (double length) {
513 return ITextHelper.createCell(LEN + toLength(length), PdfPCell.BOTTOM);
517 * Create a mass formatted cell.
519 * @param mass the mass, in default mass units
521 * @return a PdfPCell that is formatted with the mass
523 protected PdfPCell createMassCell (double mass) {
524 return ITextHelper.createCell(MASS + toMass(mass), PdfPCell.BOTTOM);
528 * Create a (shroud) line count formatted cell.
530 * @param count the number of shroud lines
532 * @return a PdfPCell that is formatted with the line count
534 protected PdfPCell createLinesCell (int count) {
535 return ITextHelper.createCell(LINES + count, PdfPCell.BOTTOM);
539 * Create a cell formatted for a name (or any string for that matter).
541 * @param v the string to format into a PDF cell
542 * @param withIndent if true, then an indention is made scaled to the level of the part in the parent hierarchy
544 * @return a PdfPCell that is formatted with the string <code>v</code>
546 protected PdfPCell createNameCell (String v, boolean withIndent) {
547 PdfPCell result = new PdfPCell();
548 result.setBorder(Rectangle.BOTTOM);
549 Chunk c = new Chunk();
550 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
552 for (int x = 0; x < (level - 2) * 10; x++) {
557 result.setColspan(2);
558 result.addElement(c);
563 * Create a cell that describes a material.
565 * @param material the material
567 * @return a PdfPCell that is formatted with a description of the material
569 protected PdfPCell createMaterialCell (Material material) {
570 PdfPCell cell = ITextHelper.createCell();
571 cell.setLeading(13f, 0);
573 Chunk c = new Chunk();
574 c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
575 c.append(toMaterialName(material));
577 Chunk density = new Chunk();
578 density.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
579 density.append(toMaterialDensity(material));
580 cell.addElement(density);
585 * Get the icon of the particular type of rocket component and conver it to an image in a PDF cell.
587 * @param visitable the rocket component to create a cell with it's image
589 * @return a PdfPCell that is just an image that can be put into a PDF
591 protected PdfPCell iconToImage (final RocketComponent visitable) {
592 if (visitable != null) {
593 final ImageIcon icon = (ImageIcon) ComponentIcons.getLargeIcon(visitable.getClass());
596 Image im = Image.getInstance(icon.getImage(), null);
598 im.scaleToFit(icon.getIconWidth() * 0.6f, icon.getIconHeight() * 0.6f);
599 PdfPCell cell = new PdfPCell(im);
600 cell.setFixedHeight(icon.getIconHeight() * 0.6f);
601 cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
602 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
603 cell.setBorder(PdfPCell.NO_BORDER);
608 catch (Exception e) {
611 PdfPCell cell = new PdfPCell();
612 cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
613 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
614 cell.setBorder(PdfPCell.NO_BORDER);
619 * Format the length as a displayable string.
621 * @param length the length (assumed to be in default length units)
623 * @return a string representation of the length with unit abbreviation
625 protected String toLength (double length) {
626 final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
627 return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(length)) + defaultUnit.toString();
631 * Format the mass as a displayable string.
633 * @param mass the mass (assumed to be in default mass units)
635 * @return a string representation of the mass with mass abbreviation
637 protected String toMass (double mass) {
638 final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
639 return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(mass)) + defaultUnit.toString();
643 * Get a displayable string of the material's name.
645 * @param material the material to output
647 * @return the material name
649 protected String toMaterialName (Material material) {
650 return material.getName();
654 * Format the material density as a displayable string.
656 * @param material the material to output
658 * @return a string representation of the material density
660 protected String toMaterialDensity (Material material) {
661 return " (" + material.getType().getUnitGroup().getDefaultUnit().toStringUnit(material.getDensity()) + ")";