JAL-4159 sometimes the progress bar may not be available - null check
[jalview.git] / src / jalview / gui / ProgressBar.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.util.MessageManager;
24
25 import java.awt.BorderLayout;
26 import java.awt.Component;
27 import java.awt.Container;
28 import java.awt.GridLayout;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ActionListener;
31 import java.util.Hashtable;
32 import java.util.Map;
33
34 import javax.swing.JButton;
35 import javax.swing.JLabel;
36 import javax.swing.JPanel;
37 import javax.swing.JProgressBar;
38 import javax.swing.SwingUtilities;
39
40 /**
41  * A class to manage multiple progress bars embedded in a JPanel.
42  */
43 /*
44  * Refactored from code duplicated in AlignFrame, PCAPanel, WebserviceInfo.
45  */
46 public class ProgressBar implements IProgressIndicator
47 {
48   /*
49    * Progress bars in progress, keyed by any arbitrary long value
50    */
51   Map<Long, JPanel> progressBars;
52
53   /*
54    * Optional handlers for the progress bars
55    */
56   Map<Long, IProgressIndicatorHandler> progressBarHandlers;
57
58   /*
59    * The panel containing the progress bars - must have GridLayout
60    */
61   private JPanel statusPanel;
62
63   /*
64    * Optional label where a status update message can be written on completion
65    * of progress
66    */
67   private JLabel statusBar;
68
69   /**
70    * Constructor. Note that the container of the progress bars, and the status
71    * bar to which an optional completion message is written, should be unchanged
72    * for the lifetime of this object for consistent behaviour.
73    * 
74    * @param container
75    *          the panel holding the progress bars; must have GridLayout manager
76    * @param statusBar
77    *          an optional place to write a message when a progress bar is
78    *          removed
79    */
80   public ProgressBar(JPanel container, JLabel statusBar)
81   {
82     if (container == null)
83     {
84       throw new NullPointerException();
85     }
86     if (!GridLayout.class
87             .isAssignableFrom(container.getLayout().getClass()))
88     {
89       throw new IllegalArgumentException("Container must have GridLayout");
90     }
91     this.statusPanel = container;
92     this.statusBar = statusBar;
93     this.progressBars = new Hashtable<>();
94     this.progressBarHandlers = new Hashtable<>();
95
96   }
97
98   /**
99    * Returns true if any progress bars are still active
100    * 
101    * @return
102    */
103   @Override
104   public boolean operationInProgress()
105   {
106     if (progressBars != null && progressBars.size() > 0)
107     {
108       return true;
109     }
110     return false;
111   }
112
113   /**
114    * First call for a given id will show the message as a new progress bar. A
115    * second call with the same id will remove it. The 'removal' message is
116    * written to the status bar field (if neither is null).
117    * 
118    * To avoid progress bars being left orphaned, ensure their removal is
119    * performed in a finally block if there is any risk of an error during
120    * execution.
121    */
122   @Override
123   public void setProgressBar(final String message, final long id)
124   {
125     SwingUtilities.invokeLater(new Runnable()
126     {
127       @Override
128       public void run()
129       {
130         JPanel progressPanel = progressBars.get(id);
131         if (progressPanel != null)
132         {
133           /*
134            * Progress bar is displayed for this id - remove it now, and any handler
135            */
136           progressBars.remove(id);
137           if (message != null && statusBar != null)
138           {
139             statusBar.setText(message);
140           }
141           if (progressBarHandlers.containsKey(id))
142           {
143             progressBarHandlers.remove(id);
144           }
145           removeRow(progressPanel);
146         }
147         else
148         {
149           /*
150            * No progress bar for this id - add one now
151            */
152           progressPanel = new JPanel(new BorderLayout(10, 5));
153
154           JProgressBar progressBar = new JProgressBar();
155           progressBar.setIndeterminate(true);
156
157           progressPanel.add(new JLabel(message), BorderLayout.WEST);
158           progressPanel.add(progressBar, BorderLayout.CENTER);
159
160           addRow(progressPanel);
161
162           progressBars.put(id, progressPanel);
163         }
164
165         refreshLayout();
166       }
167     });
168
169   }
170
171   /**
172    * Lays out progress bar container hierarchy
173    */
174   protected void refreshLayout()
175   {
176     /*
177      * lay out progress bar container hierarchy
178      */
179     Component root = SwingUtilities.getRoot(statusPanel);
180     if (root != null)
181     {
182       root.validate();
183     }
184   }
185
186   /**
187    * Remove one row with a progress bar, in a thread-safe manner
188    * 
189    * @param progressPanel
190    */
191   protected void removeRow(JPanel progressPanel)
192   {
193     synchronized (statusPanel)
194     {
195       statusPanel.remove(progressPanel);
196       GridLayout layout = (GridLayout) statusPanel.getLayout();
197       layout.setRows(layout.getRows() - 1);
198       statusPanel.remove(progressPanel);
199     }
200   }
201
202   /**
203    * Add one row with a progress bar, in a thread-safe manner
204    * 
205    * @param progressPanel
206    */
207   protected void addRow(JPanel progressPanel)
208   {
209     synchronized (statusPanel)
210     {
211       GridLayout layout = (GridLayout) statusPanel.getLayout();
212       layout.setRows(layout.getRows() + 1);
213       statusPanel.add(progressPanel);
214     }
215   }
216
217   /**
218    * Add a 'Cancel' handler for the given progress bar id. This should be called
219    * _after_ setProgressBar to have any effect.
220    */
221   @Override
222   public void registerHandler(final long id,
223           final IProgressIndicatorHandler handler)
224   {
225     final IProgressIndicator us = this;
226
227     SwingUtilities.invokeLater(new Runnable()
228     {
229       @Override
230       public void run()
231       {
232         final JPanel progressPanel = progressBars.get(id);
233         if (progressPanel == null)
234         {
235           jalview.bin.Console.errPrintln(
236                   "call setProgressBar before registering the progress bar's handler.");
237           return;
238         }
239
240         /*
241          * Nothing useful to do if not a Cancel handler
242          */
243         if (!handler.canCancel())
244         {
245           return;
246         }
247
248         progressBarHandlers.put(id, handler);
249         JButton cancel = new JButton(
250                 MessageManager.getString("action.cancel"));
251         cancel.addActionListener(new ActionListener()
252         {
253
254           @Override
255           public void actionPerformed(ActionEvent e)
256           {
257             handler.cancelActivity(id);
258             us.setProgressBar(MessageManager
259                     .formatMessage("label.cancelled_params", new Object[]
260                     { ((JLabel) progressPanel.getComponent(0)).getText() }),
261                     id);
262           }
263         });
264         progressPanel.add(cancel, BorderLayout.EAST);
265         refreshLayout();
266
267       }
268     });
269   }
270
271   /*
272    *
273    */
274   public JProgressBar getProgressBar(long id)
275   {
276     Container progBar = progressBars.get(id);
277     if (progBar==null || progBar.getComponentCount()==0)
278     {
279       return null;
280     }
281     for (Component component : progBar.getComponents())
282     {
283       if (component.getClass().equals(JProgressBar.class))
284       {
285         return (JProgressBar) component;
286       }
287     }
288     return null;
289   }
290
291 }