+package jalview.gui;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Hashtable;
+import java.util.Map;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.SwingUtilities;
+
+import jalview.util.MessageManager;
+
+/**
+ * A class to manage multiple progress bars embedded in a JPanel.
+ */
+/*
+ * Refactored from code duplicated in AlignFrame, PCAPanel, WebserviceInfo.
+ */
+public class ProgressBar implements IProgressIndicator
+{
+ /*
+ * Progress bars in progress, keyed by any arbitrary long value
+ */
+ Map<Long, JPanel> progressBars;
+
+ /*
+ * Optional handlers for the progress bars
+ */
+ Map<Long, IProgressIndicatorHandler> progressBarHandlers;
+
+ /*
+ * The panel containing the progress bars - must have GridLayout
+ */
+ private JPanel statusPanel;
+
+ /*
+ * Optional label where a status update message can be written on completion
+ * of progress
+ */
+ private JLabel statusBar;
+
+ /**
+ * Constructor. Note that the container of the progress bars, and the status
+ * bar to which an optional completion message is written, should be unchanged
+ * for the lifetime of this object for consistent behaviour.
+ *
+ * @param container
+ * the panel holding the progress bars; must have GridLayout manager
+ * @param statusBar
+ * an optional place to write a message when a progress bar is
+ * removed
+ */
+ public ProgressBar(JPanel container, JLabel statusBar)
+ {
+ if (container == null)
+ {
+ throw new NullPointerException();
+ }
+ if (!GridLayout.class
+ .isAssignableFrom(container.getLayout().getClass()))
+ {
+ throw new IllegalArgumentException("Container must have GridLayout");
+ }
+ this.statusPanel = container;
+ this.statusBar = statusBar;
+ this.progressBars = new Hashtable<Long, JPanel>();
+ this.progressBarHandlers = new Hashtable<Long, IProgressIndicatorHandler>();
+
+ }
+
+ /**
+ * Returns true if any progress bars are still active
+ *
+ * @return
+ */
+ @Override
+ public boolean operationInProgress()
+ {
+ if (progressBars != null && progressBars.size() > 0)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * First call for a given id will show the message as a new progress bar. A
+ * second call with the same id will remove it. The 'removal' message is
+ * written to the status bar field (if neither is null).
+ *
+ * To avoid progress bars being left orphaned, ensure their removal is
+ * performed in a finally block if there is any risk of an error during
+ * execution.
+ */
+ @Override
+ public void setProgressBar(String message, long id)
+ {
+ Long longId = Long.valueOf(id);
+
+ JPanel progressPanel = progressBars.get(longId);
+ if (progressPanel != null)
+ {
+ /*
+ * Progress bar is displayed for this id - remove it now, and any handler
+ */
+ progressBars.remove(id);
+ if (message != null && statusBar != null)
+ {
+ statusBar.setText(message);
+ }
+ if (progressBarHandlers.containsKey(longId))
+ {
+ progressBarHandlers.remove(longId);
+ }
+ removeRow(progressPanel);
+ }
+ else
+ {
+ /*
+ * No progress bar for this id - add one now
+ */
+ progressPanel = new JPanel(new BorderLayout(10, 5));
+
+ JProgressBar progressBar = new JProgressBar();
+ progressBar.setIndeterminate(true);
+
+ progressPanel.add(new JLabel(message), BorderLayout.WEST);
+ progressPanel.add(progressBar, BorderLayout.CENTER);
+
+ addRow(progressPanel);
+
+ progressBars.put(longId, progressPanel);
+ }
+
+ refreshLayout();
+ }
+
+ /**
+ * Lays out progress bar container hierarchy
+ */
+ protected void refreshLayout()
+ {
+ /*
+ * lay out progress bar container hierarchy
+ */
+ SwingUtilities.getRoot(statusPanel).validate();
+ }
+
+ /**
+ * Remove one row with a progress bar, in a thread-safe manner
+ *
+ * @param progressPanel
+ */
+ protected void removeRow(JPanel progressPanel)
+ {
+ synchronized (statusPanel)
+ {
+ statusPanel.remove(progressPanel);
+ GridLayout layout = (GridLayout) statusPanel.getLayout();
+ layout.setRows(layout.getRows() - 1);
+ statusPanel.remove(progressPanel);
+ }
+ }
+
+ /**
+ * Add one row with a progress bar, in a thread-safe manner
+ *
+ * @param progressPanel
+ */
+ protected void addRow(JPanel progressPanel)
+ {
+ synchronized (statusPanel)
+ {
+ GridLayout layout = (GridLayout) statusPanel.getLayout();
+ layout.setRows(layout.getRows() + 1);
+ statusPanel.add(progressPanel);
+ }
+ }
+
+ /**
+ * Add a 'Cancel' handler for the given progress bar id. This should be called
+ * _after_ setProgressBar to have any effect.
+ */
+ @Override
+ public void registerHandler(final long id,
+ final IProgressIndicatorHandler handler)
+ {
+ Long longId = Long.valueOf(id);
+ final JPanel progressPanel = progressBars.get(longId);
+ if (progressPanel == null)
+ {
+ System.err
+ .println("call setProgressBar before registering the progress bar's handler.");
+ return;
+ }
+
+ /*
+ * Nothing useful to do if not a Cancel handler
+ */
+ if (!handler.canCancel())
+ {
+ return;
+ }
+
+ progressBarHandlers.put(longId, handler);
+ JButton cancel = new JButton(MessageManager.getString("action.cancel"));
+ final IProgressIndicator us = this;
+ cancel.addActionListener(new ActionListener()
+ {
+
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ handler.cancelActivity(id);
+ us.setProgressBar(MessageManager.formatMessage(
+ "label.cancelled_params", new Object[]
+ { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
+ }
+ });
+ progressPanel.add(cancel, BorderLayout.EAST);
+ refreshLayout();
+ }
+
+}