Updates
[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                                 cancelled = true;
71                                 worker.cancel(true);
72                                 close();
73                         }
74                 });
75                 panel.add(cancel, "right");
76                 
77                 this.add(panel);
78                 this.setMinimumSize(new Dimension(250,100));
79                 this.pack();
80                 this.setLocationRelativeTo(parent);
81                 this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
82         }
83
84         @Override
85         public void propertyChange(PropertyChangeEvent evt) {
86                 if (worker.getState() == SwingWorker.StateValue.DONE) {
87                         close();
88                 }
89                 progressBar.setValue(worker.getProgress());
90         }
91         
92         private void close() {
93                 worker.removePropertyChangeListener(this);
94                 this.setVisible(false);
95         }
96         
97         
98         /**
99          * Run a SwingWorker and if necessary show a dialog displaying the progress of
100          * the worker.  The progress information is obtained from the SwingWorker's
101          * progress property.  The dialog is shown only if the worker is estimated to
102          * take a notable amount of time.
103          * <p>
104          * The dialog contains a cancel button.  Clicking it will call
105          * <code>worker.cancel(true)</code> and close the dialog immediately.
106          * 
107          * @param parent        the parent window for the dialog, or <code>null</code>.
108          * @param title         the title for the dialog.
109          * @param label         an additional label for the dialog, or <code>null</code>.
110          * @param worker        the SwingWorker to execute.
111          * @return                      <code>true</code> if the worker has completed normally,
112          *                                      <code>false</code> if the user cancelled the operation
113          */
114         public static boolean runWorker(Window parent, String title, String label,
115                         SwingWorker<?,?> worker) {
116                 
117                 // Start timing the worker
118                 final long startTime = System.currentTimeMillis();
119                 worker.execute();
120
121                 // Monitor worker thread before opening the dialog
122                 while (true) {
123                         
124                         try {
125                                 Thread.sleep(DELAY);
126                         } catch (InterruptedException e) {
127                                 // Should never occur
128                                 e.printStackTrace();
129                         }
130                         
131                         if (worker.isDone()) {
132                                 // Worker has completed within time limits
133                                 return true;
134                         }
135                         
136                         // Check whether enough time has gone to get realistic estimate
137                         long elapsed = System.currentTimeMillis() - startTime;
138                         if (elapsed < ESTIMATION_DELAY)
139                                 continue;
140                         
141                         
142                         // Calculate and check estimated remaining time
143                         int progress = MathUtil.clamp(worker.getProgress(), 1, 100); // Avoid div-by-zero
144                         long estimate = elapsed * 100 / progress;
145                         long remaining = estimate - elapsed;
146                         
147                         if (estimate >= TOTAL_TIME_FOR_DIALOG)
148                                 break;
149                         
150                         if (remaining >= REMAINING_TIME_FOR_DIALOG)
151                                 break;
152                 }
153                 
154                 
155                 // Dialog is required
156                 
157                 SwingWorkerDialog dialog = new SwingWorkerDialog(parent, title, label, worker);
158                 dialog.setVisible(true);
159                 
160                 return !dialog.cancelled;
161         }
162 }