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