DGP - 1st printing
[debian/openrocket] / src / net / sf / openrocket / gui / dialogs / PrintPanel.java
1 /*
2  * PrintPanel.java
3  */
4 package net.sf.openrocket.gui.dialogs;
5
6 import net.miginfocom.swing.MigLayout;
7 import net.sf.openrocket.document.OpenRocketDocument;
8 import net.sf.openrocket.gui.components.ColorChooser;
9 import net.sf.openrocket.gui.print.PrintController;
10 import net.sf.openrocket.gui.print.PrintUtilities;
11 import net.sf.openrocket.gui.print.PrintableContext;
12 import net.sf.openrocket.gui.print.TemplateProperties;
13 import net.sf.openrocket.gui.print.components.CheckTreeManager;
14 import net.sf.openrocket.gui.print.components.RocketPrintTree;
15 import net.sf.openrocket.logging.LogHelper;
16 import net.sf.openrocket.rocketcomponent.Rocket;
17 import net.sf.openrocket.startup.Application;
18
19 import javax.print.attribute.PrintRequestAttributeSet;
20 import javax.print.attribute.standard.MediaSizeName;
21 import javax.swing.JButton;
22 import javax.swing.JCheckBox;
23 import javax.swing.JColorChooser;
24 import javax.swing.JComponent;
25 import javax.swing.JDialog;
26 import javax.swing.JFileChooser;
27 import javax.swing.JOptionPane;
28 import javax.swing.JPanel;
29 import javax.swing.JScrollPane;
30 import javax.swing.UIManager;
31 import javax.swing.event.TreeSelectionEvent;
32 import javax.swing.event.TreeSelectionListener;
33 import javax.swing.filechooser.FileFilter;
34 import javax.swing.tree.TreeNode;
35 import javax.swing.tree.TreePath;
36 import java.awt.Color;
37 import java.awt.Desktop;
38 import java.awt.event.ActionEvent;
39 import java.awt.event.ActionListener;
40 import java.io.ByteArrayOutputStream;
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.util.Enumeration;
45 import java.util.Iterator;
46
47 /**
48  * This class isolates the Swing components used to create a panel that is added to the standard Java print dialog.
49  */
50 public class PrintPanel extends JPanel implements TreeSelectionListener {
51
52     private static final LogHelper log = Application.getLogger();
53
54     private final RocketPrintTree stagedTree;
55     private final RocketPrintTree noStagedTree;
56     private OpenRocketDocument rocDoc;
57     private RocketPrintTree currentTree;
58     private boolean bDesktopSupported = false;
59     private Desktop desktop;
60     private PrintDialog printDialog;
61
62     JButton previewButton;
63     JButton saveAsPDF;
64     
65     /**
66      * Constructor.
67      *
68      * @param orDocument the OR rocket container
69      * @param theParent  the OR parent print dialog
70      */
71     public PrintPanel (OpenRocketDocument orDocument, PrintDialog theParent) {
72
73         super(new MigLayout("fill, gap rel unrel"));
74
75         // before any Desktop APIs are used, first check whether the API is
76         // supported by this particular VM on this particular host
77         if (Desktop.isDesktopSupported()) {
78             bDesktopSupported = true;
79             desktop = Desktop.getDesktop();
80         }
81
82         printDialog = theParent;
83         rocDoc = orDocument;
84         Rocket rocket = orDocument.getRocket();
85
86         noStagedTree = RocketPrintTree.create(rocket.getName());
87         noStagedTree.setShowsRootHandles(false);
88         CheckTreeManager ctm = new net.sf.openrocket.gui.print.components.CheckTreeManager(noStagedTree);
89         ctm.addTreeSelectionListener(this);
90
91         final int stages = rocket.getStageCount();
92
93         if (stages > 1) {
94             stagedTree = RocketPrintTree.create(rocket.getName(), rocket.getChildren());
95             ctm = new CheckTreeManager(stagedTree);
96             stagedTree.setShowsRootHandles(false);
97             ctm.addTreeSelectionListener(this);
98         }
99         else {
100             stagedTree = noStagedTree;
101         }
102         currentTree = stagedTree;
103
104         final JScrollPane scrollPane = new JScrollPane(stagedTree);
105         add(scrollPane, "width 475!, wrap");
106
107         final JCheckBox sortByStage = new JCheckBox("Show By Stage");
108         sortByStage.setEnabled(stages > 1);
109         sortByStage.setSelected(stages > 1);
110         sortByStage.addActionListener(new ActionListener() {
111             public void actionPerformed (ActionEvent e) {
112                 if (sortByStage.isEnabled()) {
113                     if (((JCheckBox) e.getSource()).isSelected()) {
114                         scrollPane.setViewportView(stagedTree);
115                         stagedTree.setExpandsSelectedPaths(true);
116                         currentTree = stagedTree;
117                     }
118                     else {
119                         scrollPane.setViewportView(noStagedTree);
120                         noStagedTree.setExpandsSelectedPaths(true);
121                         currentTree = noStagedTree;
122                     }
123                 }
124             }
125         });
126         add(sortByStage, "wrap");
127
128         saveAsPDF = new JButton("Save as PDF");
129         saveAsPDF.addActionListener(new ActionListener() {
130             @Override
131             public void actionPerformed (ActionEvent e) {
132                 onSavePDF(PrintPanel.this);
133             }
134         });
135         add(saveAsPDF, "span 2, tag save");
136
137         previewButton = new JButton("Preview");
138         previewButton.addActionListener(new ActionListener() {
139             @Override
140             public void actionPerformed (ActionEvent e) {
141                 onPreview();
142             }
143         });
144         add(previewButton, "x 150");
145
146         JButton settingsButton = new JButton("Settings");
147         settingsButton.addActionListener(new ActionListener() {
148             @Override
149             public void actionPerformed (ActionEvent e) {
150                 PrintSettingsDialog settingsDialog = new PrintSettingsDialog(printDialog.getDialog());
151                 settingsDialog.setVisible(true);
152             }
153         });
154         add(settingsButton, "x 400");
155
156         expandAll(currentTree, true);
157         if (currentTree != noStagedTree) {
158             expandAll(noStagedTree, true);
159         }
160         setVisible(true);
161     }
162
163     /**
164      * The title of the tab that gets displayed for this panel, when placed in the print dialog.
165      *
166      * @return a title
167      */
168     public String getTitle () {
169         return "Rocket";
170     }
171
172     @Override 
173     public void valueChanged (final TreeSelectionEvent e) {
174         final TreePath path = e.getNewLeadSelectionPath();
175         if (path != null){
176             previewButton.setEnabled(true);
177             saveAsPDF.setEnabled(true);
178         }
179         else {
180             previewButton.setEnabled(false);
181             saveAsPDF.setEnabled(false);
182         }
183     }
184
185     /**
186      * If expand is true, expands all nodes in the tree. Otherwise, collapses all nodes in the theTree.
187      *
188      * @param theTree   the tree to expand/contract
189      * @param expand expand if true, contract if not
190      */
191     public void expandAll (RocketPrintTree theTree, boolean expand) {
192         TreeNode root = (TreeNode) theTree.getModel().getRoot();
193         // Traverse theTree from root 
194         expandAll(theTree, new TreePath(root), expand);
195     }
196
197     /**
198      * Recursively walk a tree, and if expand is true, expands all nodes in the tree. Otherwise, collapses all nodes in
199      * the theTree.
200      *
201      * @param theTree   the tree to expand/contract
202      * @param parent the node to iterate/recurse over
203      * @param expand expand if true, contract if not
204      */
205     private void expandAll (RocketPrintTree theTree, TreePath parent, boolean expand) {
206         theTree.addSelectionPath(parent);
207         // Traverse children 
208         TreeNode node = (TreeNode) parent.getLastPathComponent();
209         if (node.getChildCount() >= 0) {
210             for (Enumeration e = node.children(); e.hasMoreElements();) {
211                 TreeNode n = (TreeNode) e.nextElement();
212                 TreePath path = parent.pathByAddingChild(n);
213                 expandAll(theTree, path, expand);
214             }
215         }
216         // Expansion or collapse must be done bottom-up 
217         if (expand) {
218             theTree.expandPath(parent);
219         }
220         else {
221             theTree.collapsePath(parent);
222         }
223     }
224
225     /**
226      * Get a media size name (the name of a paper size).  If no page size is selected, it will default to the locale
227      * specific page size (LETTER in North America, A4 elsewhere).
228      *
229      * @return the name of a page size
230      */
231     private MediaSizeName getMediaSize () {
232         MediaSizeName paperSize = getMediaSize(printDialog.getAttributes());
233         if (paperSize == null) {
234             paperSize = PrintUtilities.getDefaultMedia().getMediaSizeName();
235         }
236         return paperSize;
237     }
238
239     /**
240      * Get the media size name (the name of a paper size) as selected by the user.
241      *
242      * @param atts the set of selected printer attributes
243      *
244      * @return a media size name, may be null
245      */
246     private MediaSizeName getMediaSize (PrintRequestAttributeSet atts) {
247         return (MediaSizeName) atts.get(javax.print.attribute.standard.Media.class);
248     }
249
250     /**
251      * Generate a report using a temporary file.  The file will be deleted upon JVM exit.
252      *
253      * @param paper the name of the paper size
254      *
255      * @return a file, populated with the "printed" output (the rocket info)
256      *
257      * @throws IOException thrown if the file could not be generated
258      */
259     private File generateReport (MediaSizeName paper) throws IOException {
260         final File f = File.createTempFile("oro", ".pdf");
261         f.deleteOnExit();
262         return generateReport(f, paper);
263     }
264
265     /**
266      * Generate a report to a specified file.
267      *
268      * @param f     the file to which rocket data will be written
269      * @param paper the name of the paper size
270      *
271      * @return a file, populated with the "printed" output (the rocket info)
272      *
273      * @throws IOException thrown if the file could not be generated
274      */
275     private File generateReport (File f, MediaSizeName paper) throws IOException {
276         Iterator<PrintableContext> toBePrinted = currentTree.getToBePrinted();
277         new PrintController().print(rocDoc, toBePrinted, new FileOutputStream(f), paper);
278         return f;
279     }
280
281     /**
282      * Generate a report to a byte array output stream.
283      *
284      * @return a stream populated with the "printed" output (the rocket info)
285      */
286     public ByteArrayOutputStream generateReport() {
287         Iterator<PrintableContext> toBePrinted = currentTree.getToBePrinted();
288         ByteArrayOutputStream baos = new ByteArrayOutputStream();
289         new PrintController().print(rocDoc, toBePrinted, baos, getMediaSize ());
290         return baos;
291     }
292
293     /**
294      * Handler for when the Preview button is clicked.
295      */
296     private void onPreview () {
297         if (bDesktopSupported) {
298             try {
299                 MediaSizeName paperSize = getMediaSize();
300                 File f = generateReport(paperSize);
301                 desktop.open(f);
302             }
303             catch (IOException e) {
304                 log.error("Could not create temporary file for previewing.", e);
305                 JOptionPane.showMessageDialog(this, "Could not create a temporary file for previewing.",
306                                               "Error creating file", JOptionPane.ERROR_MESSAGE);
307             }
308         }
309         else {
310             JOptionPane.showMessageDialog(this,
311                                           "Your environment does not support automatically opening the default PDF viewer.",
312                                           "Error creating file", JOptionPane.INFORMATION_MESSAGE);
313         }
314     }
315
316     /**
317      * Handler for when the "Save as PDF" button is clicked.
318      *
319      * @param p the component to parent the save dialog to
320      */
321     private void onSavePDF (JComponent p) {
322
323         JFileChooser chooser = new JFileChooser();
324         // Note: source for ExampleFileFilter can be found in FileChooserDemo,
325         // under the demo/jfc directory in the Java 2 SDK, Standard Edition.
326         FileFilter filter = new FileFilter() {
327
328             //Accept all directories and all pdf files.
329             public boolean accept (File f) {
330                 return true;
331             }
332
333             //The description of this filter
334             public String getDescription () {
335                 return "pdf";
336             }
337         };
338         chooser.setFileFilter(filter);
339         int returnVal = chooser.showSaveDialog(p);
340         if (returnVal == JFileChooser.APPROVE_OPTION) {
341
342             try {
343                 String fname = chooser.getSelectedFile().getCanonicalPath();
344                 if (!getExtension(fname).equals("pdf")) {
345                     fname = fname + ".pdf";
346                 }
347                 File f = new File(fname);
348                 generateReport(f, getMediaSize());
349             }
350             catch (IOException e) {
351                 e.printStackTrace();
352             }
353         }
354     }
355
356     /**
357      * Get the extension of a file.
358      */
359     private static String getExtension (String s) {
360         String ext = null;
361         int i = s.lastIndexOf('.');
362
363         if (i > 0 && i < s.length() - 1) {
364             ext = s.substring(i + 1).toLowerCase();
365         }
366         return ext != null ? ext : "";
367     }
368 }
369
370 /**
371  * This class is a dialog for displaying advanced settings for printing rocket related info.
372  */
373 class PrintSettingsDialog extends JDialog {
374
375     /**
376      * The fill color chooser.
377      */
378     private ColorChooser fill;
379
380     /**
381      * The line color chooser.
382      */
383     private ColorChooser line;
384
385     /**
386      * Construct a dialog for setting the advanced rocket print settings.
387      *
388      * @param parent the owning dialog
389      */
390     public PrintSettingsDialog (JDialog parent) {
391         super(parent, "Advanced Settings", true);
392         setLayout(new MigLayout("fill"));
393
394         JPanel settingsPanel = new JPanel();
395         settingsPanel.setLayout(new MigLayout("gap rel"));
396
397         fill = addColorChooser(settingsPanel, "Template Fill", TemplateProperties.getFillColor());
398         line = addColorChooser(settingsPanel, "Template Line", TemplateProperties.getLineColor());
399
400         settingsPanel.add(fill);
401         settingsPanel.add(line);
402
403         add(settingsPanel, "wrap");
404
405         JButton closeButton = new JButton("Close");
406         closeButton.addActionListener(new ActionListener() {
407             @Override
408             public void actionPerformed (ActionEvent e) {
409                 UIManager.put(TemplateProperties.TEMPLATE_FILL_COLOR_PROPERTY, fill.getCurrentColor());
410                 UIManager.put(TemplateProperties.TEMPLATE_LINE_COLOR_PROPERTY, line.getCurrentColor());
411                 dispose();
412             }
413         });
414         add(closeButton, "right, gapright para");
415
416         setSize(400, 200);
417     }
418
419     /**
420      * Add a color chooser to a panel.
421      *
422      * @param panel        the parent panel to add the color chooser.
423      * @param label        the label that indicates which color property is being changed
424      * @param initialColor the initial, or current, color to display
425      *
426      * @return a swing component containing a label, a colorized field, and a button that when clicked opens a color
427      *         chooser dialog
428      */
429     private ColorChooser addColorChooser (JPanel panel, String label, Color initialColor) {
430         final JColorChooser colorChooser = new JColorChooser(initialColor);
431         return new ColorChooser(panel, colorChooser, label);
432     }
433
434 }