2 * PartsDetailVisitorStrategy.java
4 package net.sf.openrocket.gui.print.visitor;
9 import javax.swing.ImageIcon;
11 import net.sf.openrocket.gui.main.ComponentIcons;
12 import net.sf.openrocket.gui.print.ITextHelper;
13 import net.sf.openrocket.gui.print.PrintUtilities;
14 import net.sf.openrocket.gui.print.PrintableFinSet;
15 import net.sf.openrocket.logging.LogHelper;
16 import net.sf.openrocket.material.Material;
17 import net.sf.openrocket.preset.ComponentPreset;
18 import net.sf.openrocket.rocketcomponent.BodyComponent;
19 import net.sf.openrocket.rocketcomponent.BodyTube;
20 import net.sf.openrocket.rocketcomponent.Bulkhead;
21 import net.sf.openrocket.rocketcomponent.Coaxial;
22 import net.sf.openrocket.rocketcomponent.ExternalComponent;
23 import net.sf.openrocket.rocketcomponent.FinSet;
24 import net.sf.openrocket.rocketcomponent.InnerTube;
25 import net.sf.openrocket.rocketcomponent.LaunchLug;
26 import net.sf.openrocket.rocketcomponent.MassObject;
27 import net.sf.openrocket.rocketcomponent.NoseCone;
28 import net.sf.openrocket.rocketcomponent.Parachute;
29 import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
30 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
31 import net.sf.openrocket.rocketcomponent.RingComponent;
32 import net.sf.openrocket.rocketcomponent.RocketComponent;
33 import net.sf.openrocket.rocketcomponent.ShockCord;
34 import net.sf.openrocket.rocketcomponent.Stage;
35 import net.sf.openrocket.rocketcomponent.Streamer;
36 import net.sf.openrocket.rocketcomponent.Transition;
37 import net.sf.openrocket.startup.Application;
38 import net.sf.openrocket.unit.Unit;
39 import net.sf.openrocket.unit.UnitGroup;
41 import com.itextpdf.text.Chunk;
42 import com.itextpdf.text.Document;
43 import com.itextpdf.text.DocumentException;
44 import com.itextpdf.text.Element;
45 import com.itextpdf.text.Font;
46 import com.itextpdf.text.Image;
47 import com.itextpdf.text.Paragraph;
48 import com.itextpdf.text.Phrase;
49 import com.itextpdf.text.Rectangle;
50 import com.itextpdf.text.pdf.PdfPCell;
51 import com.itextpdf.text.pdf.PdfPTable;
52 import com.itextpdf.text.pdf.PdfWriter;
53 import com.itextpdf.text.pdf.draw.VerticalPositionMark;
56 * A visitor strategy for creating documentation about parts details.
58 public class PartsDetailVisitorStrategy {
63 private static final LogHelper log = Application.getLogger();
66 * The number of columns in the table.
68 private static final int TABLE_COLUMNS = 7;
71 * The parts detail is represented as an iText table.
78 protected Document document;
81 * The direct iText writer.
83 protected PdfWriter writer;
86 * The stages selected.
88 protected Set<Integer> stages;
91 * State variable to track the level of hierarchy.
93 protected int level = 0;
95 private static final String LINES = "Lines: ";
96 private static final String MASS = "Mass: ";
97 private static final String LEN = "Len: ";
98 private static final String THICK = "Thick: ";
99 private static final String INNER = "in ";
100 private static final String DIAMETER = "Dia";
101 private static final String OUTER = "out";
102 private static final String WIDTH = "Width";
103 private static final String LENGTH = "Length";
104 private static final String SHROUD_LINES = "Shroud Lines";
105 private static final String AFT_DIAMETER = "Aft Dia: ";
106 private static final String FORE_DIAMETER = "Fore Dia: ";
107 private static final String PARTS_DETAIL = "Parts Detail";
110 * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
112 * @param doc The iText document
113 * @param theWriter The direct iText writer
114 * @param theStagesToVisit The stages to be visited by this strategy
116 public PartsDetailVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
119 stages = theStagesToVisit;
120 PrintUtilities.addText(doc, PrintUtilities.BIG_BOLD, PARTS_DETAIL);
124 * Print the parts detail.
126 * @param root the root component
128 public void writeToDocument (final RocketComponent root) {
129 goDeep(root.getChildren());
133 * Recurse through the given rocket component.
135 * @param theRc an array of rocket components; all children will be visited recursively
137 protected void goDeep (final List<RocketComponent> theRc) {
139 for (RocketComponent rocketComponent : theRc) {
140 handle(rocketComponent);
146 * Add a line to the detail report based upon the type of the component.
148 * @param component the component to print the detail for
150 private void handle (RocketComponent component) {
151 //This ugly if-then-else construct is not object oriented. Originally it was an elegant, and very OO savy, design
152 //using the Visitor pattern. Unfortunately, it was misunderstood and was removed.
153 if (component instanceof Stage) {
158 document.add(ITextHelper.createPhrase(component.getName()));
159 grid = new PdfPTable(TABLE_COLUMNS);
160 grid.setWidthPercentage(100);
161 grid.setHorizontalAlignment(Element.ALIGN_LEFT);
163 catch (DocumentException e) {
166 List<RocketComponent> rc = component.getChildren();
169 else if (component instanceof LaunchLug) {
170 LaunchLug ll = (LaunchLug) component;
171 grid.addCell(iconToImage(component));
172 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
174 grid.addCell(createMaterialCell(ll.getMaterial()));
175 grid.addCell(createOuterInnerDiaCell(ll));
176 grid.addCell(createLengthCell(component.getLength()));
177 grid.addCell(createMassCell(component.getMass()));
179 else if (component instanceof NoseCone) {
180 NoseCone nc = (NoseCone) component;
181 grid.addCell(iconToImage(component));
182 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
183 grid.addCell(createMaterialCell(nc.getMaterial()));
184 grid.addCell(ITextHelper.createCell(nc.getType().getName(), PdfPCell.BOTTOM));
185 grid.addCell(createLengthCell(component.getLength()));
186 grid.addCell(createMassCell(component.getMass()));
187 List<RocketComponent> rc = component.getChildren();
190 else if (component instanceof Transition) {
191 Transition tran = (Transition) component;
192 grid.addCell(iconToImage(component));
193 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
194 grid.addCell(createMaterialCell(tran.getMaterial()));
196 Chunk fore = new Chunk(FORE_DIAMETER + toLength(tran.getForeRadius() * 2));
197 fore.setFont(PrintUtilities.NORMAL);
198 Chunk aft = new Chunk(AFT_DIAMETER + toLength(tran.getAftRadius() * 2));
199 aft.setFont(PrintUtilities.NORMAL);
200 final PdfPCell cell = ITextHelper.createCell();
201 cell.addElement(fore);
202 cell.addElement(aft);
204 grid.addCell(createLengthCell(component.getLength()));
205 grid.addCell(createMassCell(component.getMass()));
207 List<RocketComponent> rc = component.getChildren();
210 else if (component instanceof BodyTube) {
211 BodyTube bt = (BodyTube) component;
212 grid.addCell(iconToImage(component));
213 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
214 grid.addCell(createMaterialCell(bt.getMaterial()));
215 grid.addCell(createOuterInnerDiaCell(bt));
216 grid.addCell(createLengthCell(component.getLength()));
217 grid.addCell(createMassCell(component.getMass()));
218 List<RocketComponent> rc = component.getChildren();
221 else if (component instanceof FinSet) {
222 handleFins((FinSet) component);
224 else if (component instanceof BodyComponent) {
225 grid.addCell(component.getName());
227 List<RocketComponent> rc = component.getChildren();
230 else if (component instanceof ExternalComponent) {
231 ExternalComponent ext = (ExternalComponent) component;
232 grid.addCell(iconToImage(component));
233 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
235 grid.addCell(createMaterialCell(ext.getMaterial()));
236 grid.addCell(ITextHelper.createCell());
237 grid.addCell(createLengthCell(component.getLength()));
238 grid.addCell(createMassCell(component.getMass()));
240 List<RocketComponent> rc = component.getChildren();
243 else if (component instanceof InnerTube) {
244 InnerTube it = (InnerTube) component;
245 grid.addCell(iconToImage(component));
246 final PdfPCell pCell = createNameCell(component.getName(), true, component.getPresetComponent());
248 grid.addCell(createMaterialCell(it.getMaterial()));
249 grid.addCell(createOuterInnerDiaCell(it));
250 grid.addCell(createLengthCell(component.getLength()));
251 grid.addCell(createMassCell(component.getMass()));
253 List<RocketComponent> rc = component.getChildren();
256 else if (component instanceof RadiusRingComponent) {
257 RadiusRingComponent rrc = (RadiusRingComponent) component;
258 grid.addCell(iconToImage(component));
259 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
260 grid.addCell(createMaterialCell(rrc.getMaterial()));
261 if (component instanceof Bulkhead) {
262 grid.addCell(createDiaCell(rrc.getOuterRadius()*2));
265 grid.addCell(createOuterInnerDiaCell(rrc));
267 grid.addCell(createLengthCell(component.getLength()));
268 grid.addCell(createMassCell(component.getMass()));
269 List<RocketComponent> rc = component.getChildren();
272 else if (component instanceof RingComponent) {
273 RingComponent ring = (RingComponent) component;
274 grid.addCell(iconToImage(component));
275 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
276 grid.addCell(createMaterialCell(ring.getMaterial()));
277 grid.addCell(createOuterInnerDiaCell(ring));
278 grid.addCell(createLengthCell(component.getLength()));
279 grid.addCell(createMassCell(component.getMass()));
281 List<RocketComponent> rc = component.getChildren();
284 else if (component instanceof ShockCord) {
285 ShockCord ring = (ShockCord) component;
286 PdfPCell cell = ITextHelper.createCell();
287 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
288 cell.setPaddingBottom(12f);
289 grid.addCell(iconToImage(component));
290 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
291 grid.addCell(createMaterialCell(ring.getMaterial()));
293 grid.addCell(createLengthCell(ring.getCordLength()));
294 grid.addCell(createMassCell(component.getMass()));
296 else if (component instanceof Parachute) {
297 Parachute chute = (Parachute) component;
298 PdfPCell cell = ITextHelper.createCell();
299 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
300 cell.setPaddingBottom(12f);
301 grid.addCell(iconToImage(component));
302 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
303 grid.addCell(createMaterialCell(chute.getMaterial()));
304 // if (chute.hasSpillHole()) {
305 // grid.addCell(createOuterInnerDiaCell(chute.getDiameter()/2, chute.getSpillHoleDiameter()/2));
308 grid.addCell(createDiaCell(chute.getDiameter()));
310 grid.addCell(createLengthCell(component.getLength()));
311 grid.addCell(createMassCell(component.getMass()));
313 grid.addCell(iconToImage(null));
314 grid.addCell(createNameCell(SHROUD_LINES, true, component.getPresetComponent()));
315 grid.addCell(createMaterialCell(chute.getLineMaterial()));
316 grid.addCell(createLinesCell(chute.getLineCount()));
317 grid.addCell(createLengthCell(chute.getLineLength()));
320 else if (component instanceof Streamer) {
321 Streamer ring = (Streamer) component;
322 PdfPCell cell = ITextHelper.createCell();
323 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
324 cell.setPaddingBottom(12f);
325 grid.addCell(iconToImage(component));
326 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
327 grid.addCell(createMaterialCell(ring.getMaterial()));
328 grid.addCell(createStrip(ring));
329 grid.addCell(createLengthCell(component.getLength()));
330 grid.addCell(createMassCell(component.getMass()));
332 else if (component instanceof RecoveryDevice) {
333 RecoveryDevice device = (RecoveryDevice) component;
334 PdfPCell cell = ITextHelper.createCell();
335 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
336 cell.setPaddingBottom(12f);
337 grid.addCell(iconToImage(component));
338 grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
339 grid.addCell(createMaterialCell(device.getMaterial()));
341 grid.addCell(createLengthCell(component.getLength()));
342 grid.addCell(createMassCell(component.getMass()));
344 else if (component instanceof MassObject) {
345 PdfPCell cell = ITextHelper.createCell();
346 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
347 cell.setPaddingBottom(12f);
349 grid.addCell(iconToImage(component));
350 final PdfPCell nameCell = createNameCell(component.getName(), true, component.getPresetComponent());
351 nameCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
352 nameCell.setPaddingBottom(12f);
353 grid.addCell(nameCell);
355 grid.addCell(createDiaCell(((MassObject) component).getRadius() * 2));
357 grid.addCell(createMassCell(component.getMass()));
362 * Close the strategy by adding the last grid to the document.
364 public void close () {
370 catch (DocumentException e) {
371 log.error("Could not write last cell to document.", e);
376 * Create a cell to document an outer 'diameter'. This is used for components that have no inner diameter, such as
377 * a solid parachute or bulkhead.
379 * @param diameter the diameter in default length units
381 * @return a formatted cell containing the diameter
383 private PdfPCell createDiaCell (final double diameter) {
384 PdfPCell result = new PdfPCell();
385 Phrase p = new Phrase();
387 result.setVerticalAlignment(Element.ALIGN_TOP);
388 result.setBorder(Rectangle.BOTTOM);
389 Chunk c = new Chunk();
390 c.setFont(PrintUtilities.NORMAL);
395 c.setFont(PrintUtilities.SMALL);
400 c.setFont(PrintUtilities.NORMAL);
401 c.append(" " + toLength(diameter));
403 result.addElement(p);
408 * Create a PDF cell for a streamer.
410 * @param component a component that is a Coaxial
411 * @return the PDF cell that has the streamer documented
413 private PdfPCell createStrip (final Streamer component) {
414 PdfPCell result = new PdfPCell();
415 Phrase p = new Phrase();
417 result.setVerticalAlignment(Element.ALIGN_TOP);
418 result.setBorder(Rectangle.BOTTOM);
419 Chunk c = new Chunk();
420 c.setFont(PrintUtilities.NORMAL);
425 c.setFont(PrintUtilities.NORMAL);
426 c.append(" " + toLength(component.getStripLength()));
428 result.addElement(p);
430 Phrase pw = new Phrase();
433 c.setFont(PrintUtilities.NORMAL);
438 c.setFont(PrintUtilities.NORMAL);
439 c.append(" " + toLength(component.getStripWidth()));
441 result.addElement(pw);
447 * Create a PDF cell that documents both an outer and an inner diameter of a component.
449 * @param component a component that is a Coaxial
451 * @return the PDF cell that has the outer and inner diameters documented
453 private PdfPCell createOuterInnerDiaCell (final Coaxial component) {
454 return createOuterInnerDiaCell(component, INNER);
458 * Create a PDF cell that documents both an outer and an inner diameter of a component.
460 * @param component a component that is a Coaxial
461 * @param innerLabel the label to use for the inner label subscript
463 * @return the PDF cell that has the outer and inner diameters documented
465 private PdfPCell createOuterInnerDiaCell (final Coaxial component, final String innerLabel) {
466 return createOuterInnerDiaCell(component.getOuterRadius(), component.getInnerRadius(), innerLabel);
470 * Create a PDF cell that documents both an outer and an inner diameter of a component.
472 * @param outerRadius the outer radius
473 * @param innerRadius the inner radius
474 * @param innerLabel the label to use for the inner label subscript
476 * @return the PDF cell that has the outer and inner diameters documented
478 private PdfPCell createOuterInnerDiaCell (final double outerRadius, final double innerRadius, final String innerLabel) {
480 PdfPCell result = new PdfPCell();
481 Phrase p = new Phrase();
483 result.setVerticalAlignment(Element.ALIGN_TOP);
484 result.setBorder(Rectangle.BOTTOM);
485 Chunk c = new Chunk();
486 c.setFont(PrintUtilities.NORMAL);
491 c.setFont(PrintUtilities.SMALL);
496 c.setFont(PrintUtilities.NORMAL);
497 c.append(" " + toLength(outerRadius * 2));
499 createInnerDiaCell(innerRadius, result, innerLabel);
500 result.addElement(p);
505 * Add inner diameter data to a cell.
507 * @param innerRadius the inner radius
508 * @param cell the PDF cell to add the inner diameter data to
509 * @param innerLabel the label to use for the inner label subscript
511 private void createInnerDiaCell (final double innerRadius, PdfPCell cell, final String innerLabel) {
512 Phrase p = new Phrase();
514 Chunk c = new Chunk();
515 c.setFont(PrintUtilities.NORMAL);
520 c.setFont(PrintUtilities.SMALL);
521 c.append(innerLabel);
525 c.setFont(PrintUtilities.NORMAL);
526 c.append(" " + toLength(innerRadius * 2));
532 * Add PDF cells for a fin set.
534 * @param theFinSet the fin set
536 private void handleFins (FinSet theFinSet) {
539 java.awt.Image awtImage = new PrintableFinSet(theFinSet).createImage();
542 img = Image.getInstance(writer, awtImage, 0.25f);
544 catch (Exception e) {
545 log.error("Could not write image to document.", e);
548 grid.addCell(iconToImage(theFinSet));
549 grid.addCell(createNameCell(theFinSet.getName() + " (" + theFinSet.getFinCount() + ")", true));
550 grid.addCell(createMaterialCell(theFinSet.getMaterial()));
551 grid.addCell(ITextHelper.createCell(THICK + toLength(theFinSet.getThickness()), PdfPCell.BOTTOM));
552 final PdfPCell pCell = new PdfPCell();
553 pCell.setBorder(Rectangle.BOTTOM);
554 pCell.addElement(img);
556 grid.addCell(ITextHelper.createCell());
557 grid.addCell(createMassCell(theFinSet.getMass()));
559 List<RocketComponent> rc = theFinSet.getChildren();
564 * Create a length formatted cell.
566 * @param length the length, in default length units
568 * @return a PdfPCell that is formatted with the length
570 protected PdfPCell createLengthCell (double length) {
571 return ITextHelper.createCell(LEN + toLength(length), PdfPCell.BOTTOM);
575 * Create a mass formatted cell.
577 * @param mass the mass, in default mass units
579 * @return a PdfPCell that is formatted with the mass
581 protected PdfPCell createMassCell (double mass) {
582 return ITextHelper.createCell(MASS + toMass(mass), PdfPCell.BOTTOM);
586 * Create a (shroud) line count formatted cell.
588 * @param count the number of shroud lines
590 * @return a PdfPCell that is formatted with the line count
592 protected PdfPCell createLinesCell (int count) {
593 return ITextHelper.createCell(LINES + count, PdfPCell.BOTTOM);
597 * Create a cell formatted for a name (or any string for that matter).
599 * @param v the string to format into a PDF cell
600 * @param withIndent if true, then an indention is made scaled to the level of the part in the parent hierarchy
602 * @return a PdfPCell that is formatted with the string <code>v</code>
604 protected PdfPCell createNameCell (String v, boolean withIndent) {
605 return createNameCell(v, withIndent, null);
609 * Create a cell formatted for a name (or any string for that matter).
611 * @param v the string to format into a PDF cell
612 * @param withIndent if true, then an indention is made scaled to the level of the part in the parent hierarchy
613 * @param preset the component's preset, if it has one
615 * @return a PdfPCell that is formatted with the string <code>v</code>
617 protected PdfPCell createNameCell (String v, boolean withIndent, ComponentPreset preset) {
618 PdfPCell result = new PdfPCell();
619 result.setColspan(2);
620 result.setBorder(Rectangle.BOTTOM);
621 Paragraph para = new Paragraph();
622 para.setLeading(12f, 0);
623 Chunk c = new Chunk();
624 c.setFont(PrintUtilities.NORMAL);
626 new Chunk(new VerticalPositionMark(), (level - 2) * 10, true);
629 para.add(new Chunk(tab1));
634 //Add the preset's manufacturer and part no in a subscript font.
635 if (preset != null) {
636 para.add(Chunk.NEWLINE);
639 para.add(new Chunk(tab1));
641 c.setFont(PrintUtilities.SMALL);
642 c.append(preset.toString());
645 result.addElement(para);
650 * Create a cell that describes a material.
652 * @param material the material
654 * @return a PdfPCell that is formatted with a description of the material
656 protected PdfPCell createMaterialCell (Material material) {
657 PdfPCell cell = ITextHelper.createCell();
658 cell.setLeading(13f, 0);
660 Chunk c = new Chunk();
661 c.setFont(PrintUtilities.NORMAL);
662 c.append(toMaterialName(material));
664 Chunk density = new Chunk();
665 density.setFont(PrintUtilities.SMALL);
666 density.append(toMaterialDensity(material));
667 cell.addElement(density);
672 * Get the icon of the particular type of rocket component and conver it to an image in a PDF cell.
674 * @param visitable the rocket component to create a cell with it's image
676 * @return a PdfPCell that is just an image that can be put into a PDF
678 protected PdfPCell iconToImage (final RocketComponent visitable) {
679 if (visitable != null) {
680 final ImageIcon icon = (ImageIcon) ComponentIcons.getLargeIcon(visitable.getClass());
683 Image im = Image.getInstance(icon.getImage(), null);
685 im.scaleToFit(icon.getIconWidth() * 0.6f, icon.getIconHeight() * 0.6f);
686 PdfPCell cell = new PdfPCell(im);
687 cell.setFixedHeight(icon.getIconHeight() * 0.6f);
688 cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
689 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
690 cell.setBorder(PdfPCell.NO_BORDER);
695 catch (Exception e) {
698 PdfPCell cell = new PdfPCell();
699 cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
700 cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
701 cell.setBorder(PdfPCell.NO_BORDER);
706 * Format the length as a displayable string.
708 * @param length the length (assumed to be in default length units)
710 * @return a string representation of the length with unit abbreviation
712 protected String toLength (double length) {
713 final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
714 return defaultUnit.toStringUnit(length);
718 * Format the mass as a displayable string.
720 * @param mass the mass (assumed to be in default mass units)
722 * @return a string representation of the mass with mass abbreviation
724 protected String toMass (double mass) {
725 final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
726 return defaultUnit.toStringUnit(mass);
730 * Get a displayable string of the material's name.
732 * @param material the material to output
734 * @return the material name
736 protected String toMaterialName (Material material) {
737 return material.getName();
741 * Format the material density as a displayable string.
743 * @param material the material to output
745 * @return a string representation of the material density
747 protected String toMaterialDensity (Material material) {
748 return " (" + material.getType().getUnitGroup().getDefaultUnit().toStringUnit(material.getDensity()) + ")";