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