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