package net.sf.openrocket.gui.dialogs; import java.awt.Dimension; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.SwingWorker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.miginfocom.swing.MigLayout; import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.logging.Markers; import net.sf.openrocket.startup.Application; import net.sf.openrocket.util.MathUtil; /** * A modal dialog that runs specific SwingWorkers and waits until they complete. * A message and progress bar is provided and a cancel button. If the cancel button * is pressed, the currently running worker is interrupted and the later workers are not * executed. * * @author Sampo Niskanen <sampo.niskanen@iki.fi> */ public class SwingWorkerDialog extends JDialog implements PropertyChangeListener { private static final Logger log = LoggerFactory.getLogger(SwingWorkerDialog.class); private static final Translator trans = Application.getTranslator(); /** Number of milliseconds to wait at a time between checking worker status */ private static final int DELAY = 100; /** Minimum number of milliseconds to wait before estimating work length */ private static final int ESTIMATION_DELAY = 190; /** Open the dialog if estimated remaining time is longer than this */ private static final int REMAINING_TIME_FOR_DIALOG = 1000; /** Open the dialog if estimated total time is longed than this */ private static final int TOTAL_TIME_FOR_DIALOG = 2000; private final SwingWorker<?, ?> worker; private final JProgressBar progressBar; private boolean cancelled = false; private SwingWorkerDialog(Window parent, String title, String label, SwingWorker<?, ?> w) { super(parent, title, ModalityType.APPLICATION_MODAL); this.worker = w; w.addPropertyChangeListener(this); JPanel panel = new JPanel(new MigLayout("fill")); if (label != null) { panel.add(new JLabel(label), "wrap para"); } progressBar = new JProgressBar(); panel.add(progressBar, "growx, wrap para"); //// Cancel button JButton cancel = new JButton(trans.get("dlg.but.cancel")); cancel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { log.info(Markers.USER_MARKER, "User cancelled SwingWorker operation"); cancel(); } }); panel.add(cancel, "right"); this.add(panel); this.setMinimumSize(new Dimension(250, 100)); this.pack(); this.setLocationRelativeTo(parent); this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); } @Override public void propertyChange(PropertyChangeEvent evt) { if (worker.getState() == SwingWorker.StateValue.DONE) { close(); } progressBar.setValue(worker.getProgress()); } private void cancel() { cancelled = true; worker.cancel(true); close(); } private void close() { worker.removePropertyChangeListener(this); this.setVisible(false); // For some reason setVisible(false) is not always enough... this.dispose(); } /** * Run a SwingWorker and if necessary show a dialog displaying the progress of * the worker. The progress information is obtained from the SwingWorker's * progress property. The dialog is shown only if the worker is estimated to * take a notable amount of time. * <p> * The dialog contains a cancel button. Clicking it will call * <code>worker.cancel(true)</code> and close the dialog immediately. * * @param parent the parent window for the dialog, or <code>null</code>. * @param title the title for the dialog. * @param label an additional label for the dialog, or <code>null</code>. * @param worker the SwingWorker to execute. * @return <code>true</code> if the worker has completed normally, * <code>false</code> if the user cancelled the operation */ public static boolean runWorker(Window parent, String title, String label, SwingWorker<?, ?> worker) { log.info("Running SwingWorker " + worker); // Start timing the worker final long startTime = System.currentTimeMillis(); worker.execute(); // Monitor worker thread before opening the dialog while (true) { try { Thread.sleep(DELAY); } catch (InterruptedException e) { // Should never occur log.error("EDT was interrupted", e); } if (worker.isDone()) { // Worker has completed within time limits log.info("Worker completed before opening dialog"); return true; } // Check whether enough time has gone to get realistic estimate long elapsed = System.currentTimeMillis() - startTime; if (elapsed < ESTIMATION_DELAY) continue; // Calculate and check estimated remaining time int progress = MathUtil.clamp(worker.getProgress(), 1, 100); // Avoid div-by-zero long estimate = elapsed * 100 / progress; long remaining = estimate - elapsed; log.debug("Estimated run time, estimate=" + estimate + " remaining=" + remaining); if (estimate >= TOTAL_TIME_FOR_DIALOG) break; if (remaining >= REMAINING_TIME_FOR_DIALOG) break; } // Dialog is required log.info("Opening dialog for SwingWorker " + worker); SwingWorkerDialog dialog = new SwingWorkerDialog(parent, title, label, worker); dialog.setVisible(true); log.info("Worker done, cancelled=" + dialog.cancelled); return !dialog.cancelled; } }