1 package net.sf.openrocket.gui.dialogs;
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;
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;
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;
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
30 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
32 public class SwingWorkerDialog extends JDialog implements PropertyChangeListener {
33 private static final LogHelper log = Application.getLogger();
34 private static final Translator trans = Application.getTranslator();
36 /** Number of milliseconds to wait at a time between checking worker status */
37 private static final int DELAY = 100;
39 /** Minimum number of milliseconds to wait before estimating work length */
40 private static final int ESTIMATION_DELAY = 190;
42 /** Open the dialog if estimated remaining time is longer than this */
43 private static final int REMAINING_TIME_FOR_DIALOG = 1000;
45 /** Open the dialog if estimated total time is longed than this */
46 private static final int TOTAL_TIME_FOR_DIALOG = 2000;
49 private final SwingWorker<?, ?> worker;
50 private final JProgressBar progressBar;
52 private boolean cancelled = false;
55 private SwingWorkerDialog(Window parent, String title, String label, SwingWorker<?, ?> w) {
56 super(parent, title, ModalityType.APPLICATION_MODAL);
59 w.addPropertyChangeListener(this);
61 JPanel panel = new JPanel(new MigLayout("fill"));
64 panel.add(new JLabel(label), "wrap para");
67 progressBar = new JProgressBar();
68 panel.add(progressBar, "growx, wrap para");
71 JButton cancel = new JButton(trans.get("dlg.but.cancel"));
72 cancel.addActionListener(new ActionListener() {
74 public void actionPerformed(ActionEvent e) {
75 log.user("User cancelled SwingWorker operation");
79 panel.add(cancel, "right");
82 this.setMinimumSize(new Dimension(250, 100));
84 this.setLocationRelativeTo(parent);
85 this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
89 public void propertyChange(PropertyChangeEvent evt) {
90 if (worker.getState() == SwingWorker.StateValue.DONE) {
93 progressBar.setValue(worker.getProgress());
96 private void cancel() {
102 private void close() {
103 worker.removePropertyChangeListener(this);
104 this.setVisible(false);
105 // For some reason setVisible(false) is not always enough...
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.
116 * The dialog contains a cancel button. Clicking it will call
117 * <code>worker.cancel(true)</code> and close the dialog immediately.
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
126 public static boolean runWorker(Window parent, String title, String label,
127 SwingWorker<?, ?> worker) {
129 log.info("Running SwingWorker " + worker);
131 // Start timing the worker
132 final long startTime = System.currentTimeMillis();
135 // Monitor worker thread before opening the dialog
140 } catch (InterruptedException e) {
141 // Should never occur
142 log.error("EDT was interrupted", e);
145 if (worker.isDone()) {
146 // Worker has completed within time limits
147 log.info("Worker completed before opening dialog");
151 // Check whether enough time has gone to get realistic estimate
152 long elapsed = System.currentTimeMillis() - startTime;
153 if (elapsed < ESTIMATION_DELAY)
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;
162 log.debug("Estimated run time, estimate=" + estimate + " remaining=" + remaining);
164 if (estimate >= TOTAL_TIME_FOR_DIALOG)
167 if (remaining >= REMAINING_TIME_FOR_DIALOG)
172 // Dialog is required
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);
179 return !dialog.cancelled;