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