1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: JobsProgressBar.java 2230 2012-02-08 00:00:10Z dmsmith $
21   */
22  package org.crosswire.common.progress.swing;
23  
24  import java.awt.Component;
25  import java.awt.Font;
26  import java.awt.GridBagConstraints;
27  import java.awt.GridBagLayout;
28  import java.awt.GridLayout;
29  import java.io.IOException;
30  import java.io.ObjectInputStream;
31  import java.util.ArrayList;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  
37  import javax.swing.JButton;
38  import javax.swing.JPanel;
39  import javax.swing.JProgressBar;
40  import javax.swing.SwingUtilities;
41  
42  import org.crosswire.common.icu.NumberShaper;
43  import org.crosswire.common.progress.Job;
44  import org.crosswire.common.progress.JobManager;
45  import org.crosswire.common.progress.Progress;
46  import org.crosswire.common.progress.ProgressMode;
47  import org.crosswire.common.progress.WorkEvent;
48  import org.crosswire.common.progress.WorkListener;
49  import org.crosswire.common.swing.ActionFactory;
50  import org.crosswire.common.swing.CWAction;
51  import org.crosswire.common.swing.GuiUtil;
52  import org.crosswire.common.util.Logger;
53  
54  /**
55   * JobsViewPane is a small JProgressBar based viewer for current jobs.
56   * 
57   * @see gnu.lgpl.License for license details.<br>
58   *      The copyright to this program is held by it's authors.
59   * @author Joe Walker [joe at eireneh dot com]
60   * @author DM Smith [dmsmith555 at yahoo dot com]
61   */
62  public class JobsProgressBar extends JPanel implements WorkListener {
63      /**
64       * Simple ctor
65       */
66      public JobsProgressBar(boolean small) {
67          jobs = new HashMap<Progress, JobData>();
68          positions = new ArrayList<JobData>();
69          shaper = new NumberShaper();
70          actions = new ActionFactory(this);
71  
72          if (small) {
73              // They start off at 15pt (on Windows at least)
74              font = new Font("SansSerif", Font.PLAIN, 10);
75          }
76  
77          JobManager.addWorkListener(this);
78  
79          Set<Progress> current = JobManager.getJobs();
80          for (Progress job : current) {
81              addJob(job);
82          }
83  
84          this.setLayout(new GridLayout(1, 0, 2, 0));
85  
86          GuiUtil.applyDefaultOrientation(this);
87      }
88  
89      /**
90       * Create a cancel button that only shows the cancel icon. When the button
91       * is pressed the job is interrupted.
92       * 
93       * @return a custom cancel button
94       */
95      public synchronized JButton createCancelButton(Progress job) {
96          CWAction action = actions.addAction("Stop");
97          action.setSmallIcon("toolbarButtonGraphics/general/Stop16.gif");
98          action.setListener(new JobCancelListener(job));
99          return GuiUtil.flatten(new JButton(action));
100     }
101 
102     /*
103      * (non-Javadoc)
104      * 
105      * @see
106      * org.crosswire.common.progress.WorkListener#workProgressed(org.crosswire
107      * .common.progress.WorkEvent)
108      */
109     public synchronized void workProgressed(final WorkEvent ev) {
110         SwingUtilities.invokeLater(new Runnable() {
111             public void run() {
112                 Progress job = ev.getJob();
113 
114                 if (!jobs.containsKey(job)) {
115                     addJob(job);
116                 }
117 
118                 updateJob(job);
119 
120                 if (job.isFinished()) {
121                     removeJob(job);
122                 }
123             }
124         });
125     }
126 
127     /*
128      * (non-Javadoc)
129      * 
130      * @see
131      * org.crosswire.common.progress.WorkListener#workStateChanged(org.crosswire
132      * .common.progress.WorkEvent)
133      */
134     public void workStateChanged(WorkEvent ev) {
135         Progress job = (Job) ev.getSource();
136         JobData jobdata = jobs.get(job);
137         jobdata.workStateChanged(ev);
138     }
139 
140     /**
141      * Create a new set of components for the new Job
142      */
143     /* private */final synchronized void addJob(Progress job) {
144         ((Job) job).addWorkListener(this);
145 
146         int i = findEmptyPosition();
147         log.debug("adding job to panel at " + i + ": " + job.getJobName());
148 
149         JProgressBar progress = new JProgressBar();
150 //        if (job.getProgressMode() == ProgressMode.UNKNOWN) {
151 //            progress.setIndeterminate(true);
152 //        }
153         progress.setStringPainted(true);
154         progress.setToolTipText(job.getJobName());
155         progress.setBorder(null);
156         progress.setBackground(getBackground());
157         progress.setForeground(getForeground());
158         if (font != null) {
159             progress.setFont(font);
160         }
161         GuiUtil.applyDefaultOrientation(progress);
162 
163         // Dimension preferred = progress.getPreferredSize();
164         // preferred.width = 50;
165         // progress.setPreferredSize(preferred);
166 
167         JobData jobdata = new JobData(this, job, i, progress);
168         jobs.put(job, jobdata);
169         if (i >= positions.size()) {
170             positions.add(jobdata);
171         } else {
172             positions.set(i, jobdata);
173         }
174 
175         this.add(jobdata.getComponent(), i);
176         GuiUtil.refresh(this);
177         GuiUtil.applyDefaultOrientation(this);
178     }
179 
180     /**
181      * Update the job details because it has just progressed
182      */
183     protected synchronized void updateJob(Progress job) {
184         JobData jobdata = jobs.get(job);
185 
186         // At 99% the progress bar animates nicely.
187         int percent = 99;
188         StringBuilder buf = new StringBuilder(job.getSectionName());
189         if (job.getProgressMode() != ProgressMode.UNKNOWN) {
190             percent = job.getWork();
191             buf.append(": ");
192             buf.append(shaper.shape(Integer.toString(percent)));
193             buf.append('%');
194         }
195         jobdata.getProgress().setString(buf.toString());
196         jobdata.getProgress().setValue(percent);
197     }
198 
199     /**
200      * Remove the set of components from the panel
201      */
202     protected synchronized void removeJob(Progress job) {
203         ((Job) job).removeWorkListener(this);
204 
205         JobData jobdata = jobs.get(job);
206 
207         positions.set(jobdata.getIndex(), null);
208         jobs.remove(job);
209         log.debug("removing job from panel: " + jobdata.getJob().getJobName());
210 
211         this.remove(jobdata.getComponent());
212         GuiUtil.refresh(this);
213         jobdata.invalidate();
214     }
215 
216     /**
217      * Where is the next hole in the positions array
218      */
219     private int findEmptyPosition() {
220         int i = 0;
221         while (true) {
222             if (i >= positions.size()) {
223                 break;
224             }
225 
226             if (positions.get(i) == null) {
227                 break;
228             }
229 
230             i++;
231         }
232 
233         return i;
234     }
235 
236     /**
237      * Serialization support.
238      * 
239      * @param is
240      * @throws IOException
241      * @throws ClassNotFoundException
242      */
243     private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
244         actions = new ActionFactory(this);
245         is.defaultReadObject();
246     }
247 
248     /**
249      * Where we store the currently displayed jobs
250      */
251     protected transient Map<Progress, JobData> jobs;
252 
253     /**
254      * Array telling us what y position the jobs have in the window
255      */
256     private transient List<JobData> positions;
257 
258     /**
259      * The font for the progress-bars
260      */
261     private Font font;
262 
263     /**
264      * Shape numbers into locale representation.
265      */
266     private NumberShaper shaper = new NumberShaper();
267 
268     /**
269      * The home of the stop action.
270      */
271     private transient ActionFactory actions;
272 
273     /**
274      * The log stream
275      */
276     private static final Logger log = Logger.getLogger(JobsProgressBar.class);
277 
278     /**
279      * Serialization ID
280      */
281     private static final long serialVersionUID = 3257563988660663606L;
282 
283     /**
284      * A simple class to group information about a Job
285      */
286     private static class JobData implements WorkListener {
287         /**
288          * Simple ctor
289          */
290         JobData(JobsProgressBar bar, Progress job, int index, JProgressBar progress) {
291             this.bar = bar;
292             this.job = job;
293             this.index = index;
294             this.progress = progress;
295             this.comp = decorateProgressBar();
296         }
297 
298         /**
299          * ensure we can't be used again
300          */
301         void invalidate() {
302             job = null;
303             progress = null;
304             index = -1;
305         }
306 
307         /**
308          * Accessor for the Job
309          */
310         Progress getJob() {
311             return job;
312         }
313 
314         /**
315          * Accessor for the Progress Bar
316          */
317         JProgressBar getProgress() {
318             return progress;
319         }
320 
321         /**
322          *
323          */
324         public Component getComponent() {
325             return comp;
326         }
327 
328         /**
329          * Accessor for the index
330          */
331         int getIndex() {
332             return index;
333         }
334 
335         /*
336          * (non-Javadoc)
337          * 
338          * @see
339          * org.crosswire.common.progress.WorkListener#workStateChanged(org.crosswire
340          * .common.progress.WorkEvent)
341          */
342         public void workStateChanged(WorkEvent evt) {
343             if (cancelButton != null) {
344                 cancelButton.setEnabled(job.isCancelable());
345             }
346         }
347 
348         /*
349          * (non-Javadoc)
350          * 
351          * @see
352          * org.crosswire.common.progress.WorkListener#workProgressed(org.crosswire
353          * .common.progress.WorkEvent)
354          */
355         public void workProgressed(WorkEvent ev) {
356             // Don't care about progress
357         }
358 
359         /**
360          * Decorate the progress bar if the job can be interrupted. We put the
361          * cancel button in a 1 row, 2 column grid where the button is in a
362          * minimally sized fixed cell and the progress meter follows in a
363          * horizontally stretchy cell
364          */
365         private Component decorateProgressBar() {
366             if (!job.isCancelable()) {
367                 return progress;
368             }
369 
370             JPanel panel = new JPanel(new GridBagLayout());
371             GridBagConstraints gbc = new GridBagConstraints();
372             gbc.gridwidth = 1;
373             gbc.fill = GridBagConstraints.NONE;
374             cancelButton = bar.createCancelButton(job);
375             panel.add(cancelButton, gbc);
376             gbc.weightx = 1.0;
377             gbc.gridwidth = GridBagConstraints.REMAINDER;
378             gbc.fill = GridBagConstraints.HORIZONTAL;
379             panel.add(progress, gbc);
380             GuiUtil.applyDefaultOrientation(panel);
381             return panel;
382         }
383 
384         private JobsProgressBar bar;
385         private Progress job;
386         private int index;
387         private JProgressBar progress;
388         private Component comp;
389         private JButton cancelButton;
390     }
391 }
392