added svn:ignores
[debian/openrocket] / src / net / sf / openrocket / gui / dialogs / SwingWorkerDialog.java
1 package net.sf.openrocket.gui.dialogs;
2
3 import java.awt.Dimension;
4 import java.awt.Window;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.beans.PropertyChangeEvent;
8 import java.beans.PropertyChangeListener;
9
10 import javax.swing.JButton;
11 import javax.swing.JDialog;
12 import javax.swing.JLabel;
13 import javax.swing.JPanel;
14 import javax.swing.JProgressBar;
15 import javax.swing.SwingWorker;
16
17 import net.miginfocom.swing.MigLayout;
18 import net.sf.openrocket.logging.LogHelper;
19 import net.sf.openrocket.startup.Application;
20 import net.sf.openrocket.util.MathUtil;
21
22
23 /**
24  * A modal dialog that runs specific SwingWorkers and waits until they complete.
25  * A message and progress bar is provided and a cancel button.  If the cancel button
26  * is pressed, the currently running worker is interrupted and the later workers are not
27  * executed.
28  * 
29  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
30  */
31 public class SwingWorkerDialog extends JDialog implements PropertyChangeListener {
32         private static final LogHelper log = Application.getLogger();
33         
34         /** Number of milliseconds to wait at a time between checking worker status */
35         private static final int DELAY = 100;
36         
37         /** Minimum number of milliseconds to wait before estimating work length */
38         private static final int ESTIMATION_DELAY = 190;
39         
40         /** Open the dialog if estimated remaining time is longer than this */
41         private static final int REMAINING_TIME_FOR_DIALOG = 1000;
42         
43         /** Open the dialog if estimated total time is longed than this */
44         private static final int TOTAL_TIME_FOR_DIALOG = 2000;
45         
46
47         private final SwingWorker<?, ?> worker;
48         private final JProgressBar progressBar;
49         
50         private boolean cancelled = false;
51         
52         
53         private SwingWorkerDialog(Window parent, String title, String label, SwingWorker<?, ?> w) {
54                 super(parent, title, ModalityType.APPLICATION_MODAL);
55                 
56                 this.worker = w;
57                 w.addPropertyChangeListener(this);
58                 
59                 JPanel panel = new JPanel(new MigLayout("fill"));
60                 
61                 if (label != null) {
62                         panel.add(new JLabel(label), "wrap para");
63                 }
64                 
65                 progressBar = new JProgressBar();
66                 panel.add(progressBar, "growx, wrap para");
67                 
68                 JButton cancel = new JButton("Cancel");
69                 cancel.addActionListener(new ActionListener() {
70                         @Override
71                         public void actionPerformed(ActionEvent e) {
72                                 log.user("User cancelled SwingWorker operation");
73                                 cancel();
74                         }
75                 });
76                 panel.add(cancel, "right");
77                 
78                 this.add(panel);
79                 this.setMinimumSize(new Dimension(250, 100));
80                 this.pack();
81                 this.setLocationRelativeTo(parent);
82                 this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
83         }
84         
85         @Override
86         public void propertyChange(PropertyChangeEvent evt) {
87                 if (worker.getState() == SwingWorker.StateValue.DONE) {
88                         close();
89                 }
90                 progressBar.setValue(worker.getProgress());
91         }
92         
93         private void cancel() {
94                 cancelled = true;
95                 worker.cancel(true);
96                 close();
97         }
98         
99         private void close() {
100                 worker.removePropertyChangeListener(this);
101                 this.setVisible(false);
102                 // For some reason setVisible(false) is not always enough...
103                 this.dispose();
104         }
105         
106         
107         /**
108          * Run a SwingWorker and if necessary show a dialog displaying the progress of
109          * the worker.  The progress information is obtained from the SwingWorker's
110          * progress property.  The dialog is shown only if the worker is estimated to
111          * take a notable amount of time.
112          * <p>
113          * The dialog contains a cancel button.  Clicking it will call
114          * <code>worker.cancel(true)</code> and close the dialog immediately.
115          * 
116          * @param parent        the parent window for the dialog, or <code>null</code>.
117          * @param title         the title for the dialog.
118          * @param label         an additional label for the dialog, or <code>null</code>.
119          * @param worker        the SwingWorker to execute.
120          * @return                      <code>true</code> if the worker has completed normally,
121          *                                      <code>false</code> if the user cancelled the operation
122          */
123         public static boolean runWorker(Window parent, String title, String label,
124                         SwingWorker<?, ?> worker) {
125                 
126                 log.info("Running SwingWorker " + worker);
127                 
128                 // Start timing the worker
129                 final long startTime = System.currentTimeMillis();
130                 worker.execute();
131                 
132                 // Monitor worker thread before opening the dialog
133                 while (true) {
134                         
135                         try {
136                                 Thread.sleep(DELAY);
137                         } catch (InterruptedException e) {
138                                 // Should never occur
139                                 log.error("EDT was interrupted", e);
140                         }
141                         
142                         if (worker.isDone()) {
143                                 // Worker has completed within time limits
144                                 log.info("Worker completed before opening dialog");
145                                 return true;
146                         }
147                         
148                         // Check whether enough time has gone to get realistic estimate
149                         long elapsed = System.currentTimeMillis() - startTime;
150                         if (elapsed < ESTIMATION_DELAY)
151                                 continue;
152                         
153
154                         // Calculate and check estimated remaining time
155                         int progress = MathUtil.clamp(worker.getProgress(), 1, 100); // Avoid div-by-zero
156                         long estimate = elapsed * 100 / progress;
157                         long remaining = estimate - elapsed;
158                         
159                         log.debug("Estimated run time, estimate=" + estimate + " remaining=" + remaining);
160                         
161                         if (estimate >= TOTAL_TIME_FOR_DIALOG)
162                                 break;
163                         
164                         if (remaining >= REMAINING_TIME_FOR_DIALOG)
165                                 break;
166                 }
167                 
168
169                 // Dialog is required
170                 
171                 log.info("Opening dialog for SwingWorker " + worker);
172                 SwingWorkerDialog dialog = new SwingWorkerDialog(parent, title, label, worker);
173                 dialog.setVisible(true);
174                 log.info("Worker done, cancelled=" + dialog.cancelled);
175                 
176                 return !dialog.cancelled;
177         }
178 }