JAL-1769 fixed headless execution mode and created test for all possible outputs
[jalview.git] / src / jalview / gui / ProgressBar.java
1 package jalview.gui;
2
3 import jalview.util.MessageManager;
4
5 import java.awt.BorderLayout;
6 import java.awt.Component;
7 import java.awt.GridLayout;
8 import java.awt.event.ActionEvent;
9 import java.awt.event.ActionListener;
10 import java.util.Hashtable;
11 import java.util.Map;
12
13 import javax.swing.JButton;
14 import javax.swing.JLabel;
15 import javax.swing.JPanel;
16 import javax.swing.JProgressBar;
17 import javax.swing.SwingUtilities;
18
19 /**
20  * A class to manage multiple progress bars embedded in a JPanel.
21  */
22 /*
23  * Refactored from code duplicated in AlignFrame, PCAPanel, WebserviceInfo.
24  */
25 public class ProgressBar implements IProgressIndicator
26 {
27   /*
28    * Progress bars in progress, keyed by any arbitrary long value
29    */
30   Map<Long, JPanel> progressBars;
31
32   /*
33    * Optional handlers for the progress bars
34    */
35   Map<Long, IProgressIndicatorHandler> progressBarHandlers;
36
37   /*
38    * The panel containing the progress bars - must have GridLayout
39    */
40   private JPanel statusPanel;
41
42   /*
43    * Optional label where a status update message can be written on completion
44    * of progress
45    */
46   private JLabel statusBar;
47
48   /**
49    * Constructor. Note that the container of the progress bars, and the status
50    * bar to which an optional completion message is written, should be unchanged
51    * for the lifetime of this object for consistent behaviour.
52    * 
53    * @param container
54    *          the panel holding the progress bars; must have GridLayout manager
55    * @param statusBar
56    *          an optional place to write a message when a progress bar is
57    *          removed
58    */
59   public ProgressBar(JPanel container, JLabel statusBar)
60   {
61     if (container == null)
62     {
63       throw new NullPointerException();
64     }
65     if (!GridLayout.class
66             .isAssignableFrom(container.getLayout().getClass()))
67     {
68       throw new IllegalArgumentException("Container must have GridLayout");
69     }
70     this.statusPanel = container;
71     this.statusBar = statusBar;
72     this.progressBars = new Hashtable<Long, JPanel>();
73     this.progressBarHandlers = new Hashtable<Long, IProgressIndicatorHandler>();
74
75   }
76
77   /**
78    * Returns true if any progress bars are still active
79    * 
80    * @return
81    */
82   @Override
83   public boolean operationInProgress()
84   {
85     if (progressBars != null && progressBars.size() > 0)
86     {
87       return true;
88     }
89     return false;
90   }
91
92   /**
93    * First call for a given id will show the message as a new progress bar. A
94    * second call with the same id will remove it. The 'removal' message is
95    * written to the status bar field (if neither is null).
96    * 
97    * To avoid progress bars being left orphaned, ensure their removal is
98    * performed in a finally block if there is any risk of an error during
99    * execution.
100    */
101   @Override
102   public void setProgressBar(String message, long id)
103   {
104     Long longId = Long.valueOf(id);
105   
106     JPanel progressPanel = progressBars.get(longId);
107     if (progressPanel != null)
108     {
109       /*
110        * Progress bar is displayed for this id - remove it now, and any handler
111        */
112       progressBars.remove(id);
113       if (message != null && statusBar != null)
114       {
115         statusBar.setText(message);
116       }
117       if (progressBarHandlers.containsKey(longId))
118       {
119         progressBarHandlers.remove(longId);
120       }
121       removeRow(progressPanel);
122     }
123     else
124     {
125       /*
126        * No progress bar for this id - add one now
127        */
128       progressPanel = new JPanel(new BorderLayout(10, 5));
129   
130       JProgressBar progressBar = new JProgressBar();
131       progressBar.setIndeterminate(true);
132   
133       progressPanel.add(new JLabel(message), BorderLayout.WEST);
134       progressPanel.add(progressBar, BorderLayout.CENTER);
135   
136       addRow(progressPanel);
137   
138       progressBars.put(longId, progressPanel);
139     }
140
141     refreshLayout();
142   }
143
144   /**
145    * Lays out progress bar container hierarchy
146    */
147   protected void refreshLayout()
148   {
149     /*
150      * lay out progress bar container hierarchy
151      */
152     Component root = SwingUtilities.getRoot(statusPanel);
153     if (root != null)
154     {
155       root.validate();
156     }
157   }
158
159   /**
160    * Remove one row with a progress bar, in a thread-safe manner
161    * 
162    * @param progressPanel
163    */
164   protected void removeRow(JPanel progressPanel)
165   {
166     synchronized (statusPanel)
167     {
168       statusPanel.remove(progressPanel);
169       GridLayout layout = (GridLayout) statusPanel.getLayout();
170       layout.setRows(layout.getRows() - 1);
171       statusPanel.remove(progressPanel);
172     }
173   }
174
175   /**
176    * Add one row with a progress bar, in a thread-safe manner
177    * 
178    * @param progressPanel
179    */
180   protected void addRow(JPanel progressPanel)
181   {
182     synchronized (statusPanel)
183     {
184       GridLayout layout = (GridLayout) statusPanel.getLayout();
185       layout.setRows(layout.getRows() + 1);
186       statusPanel.add(progressPanel);
187     }
188   }
189
190   /**
191    * Add a 'Cancel' handler for the given progress bar id. This should be called
192    * _after_ setProgressBar to have any effect.
193    */
194   @Override
195   public void registerHandler(final long id,
196           final IProgressIndicatorHandler handler)
197   {
198     Long longId = Long.valueOf(id);
199     final JPanel progressPanel = progressBars.get(longId);
200     if (progressPanel == null)
201     {
202       System.err
203               .println("call setProgressBar before registering the progress bar's handler.");
204       return;
205     }
206
207     /*
208      * Nothing useful to do if not a Cancel handler
209      */
210     if (!handler.canCancel())
211     {
212       return;
213     }
214
215     progressBarHandlers.put(longId, handler);
216     JButton cancel = new JButton(MessageManager.getString("action.cancel"));
217     final IProgressIndicator us = this;
218     cancel.addActionListener(new ActionListener()
219     {
220
221       @Override
222       public void actionPerformed(ActionEvent e)
223       {
224         handler.cancelActivity(id);
225         us.setProgressBar(MessageManager.formatMessage(
226                 "label.cancelled_params", new Object[]
227                 { ((JLabel) progressPanel.getComponent(0)).getText() }), id);
228       }
229     });
230     progressPanel.add(cancel, BorderLayout.EAST);
231     refreshLayout();
232   }
233
234 }