JAL-3130 adapted getdown src. attempt 2. first attempt failed due to cp'ed .git files
[jalview.git] / getdown / src / getdown / launcher / src / main / java / com / threerings / getdown / launcher / StatusPanel.java
1 //
2 // Getdown - application installer, patcher and launcher
3 // Copyright (C) 2004-2018 Getdown authors
4 // https://github.com/threerings/getdown/blob/master/LICENSE
5
6 package com.threerings.getdown.launcher;
7
8 import java.awt.Color;
9 import java.awt.Dimension;
10 import java.awt.Font;
11 import java.awt.Graphics;
12 import java.awt.Graphics2D;
13 import java.awt.Image;
14 import java.awt.event.ActionEvent;
15 import java.awt.event.ActionListener;
16 import java.awt.image.ImageObserver;
17 import java.text.MessageFormat;
18 import java.util.Arrays;
19 import java.util.MissingResourceException;
20 import java.util.ResourceBundle;
21
22 import javax.swing.JComponent;
23 import javax.swing.Timer;
24
25 import com.samskivert.swing.Label;
26 import com.samskivert.swing.LabelStyleConstants;
27 import com.samskivert.swing.util.SwingUtil;
28 import com.samskivert.util.Throttle;
29
30 import com.threerings.getdown.data.Application.UpdateInterface;
31 import com.threerings.getdown.util.MessageUtil;
32 import com.threerings.getdown.util.Rectangle;
33 import com.threerings.getdown.util.StringUtil;
34
35 import static com.threerings.getdown.Log.log;
36
37 /**
38  * Displays download and patching status.
39  */
40 public final class StatusPanel extends JComponent
41     implements ImageObserver
42 {
43     public StatusPanel (ResourceBundle msgs)
44     {
45         _msgs = msgs;
46
47         // Add a bit of "throbbing" to the display by updating the number of dots displayed after
48         // our status. This lets users know things are still working.
49         _timer = new Timer(1000,
50             new ActionListener() {
51                 public void actionPerformed (ActionEvent event) {
52                     if (_status != null && !_displayError) {
53                         _statusDots = (_statusDots % 3) + 1; // 1, 2, 3, 1, 2, 3, etc.
54                         updateStatusLabel();
55                     }
56                 }
57             });
58     }
59
60     public void init (UpdateInterface ifc, RotatingBackgrounds bg, Image barimg)
61     {
62         _ifc = ifc;
63         _bg = bg;
64         Image img = _bg.getImage(_progress);
65         int width = img == null ? -1 : img.getWidth(this);
66         int height = img == null ? -1 : img.getHeight(this);
67         if (width == -1 || height == -1) {
68             Rectangle bounds = ifc.progress.union(ifc.status);
69             // assume the x inset defines the frame padding; add it on the left, right, and bottom
70             _psize = new Dimension(bounds.x + bounds.width + bounds.x,
71                                    bounds.y + bounds.height + bounds.x);
72         } else {
73             _psize = new Dimension(width, height);
74         }
75         _barimg = barimg;
76         invalidate();
77     }
78
79     @Override
80     public boolean imageUpdate (Image img, int infoflags, int x, int y, int width, int height)
81     {
82         boolean updated = false;
83         if ((infoflags & WIDTH) != 0) {
84             _psize.width = width;
85             updated = true;
86         }
87         if ((infoflags & HEIGHT) != 0) {
88             _psize.height = height;
89             updated = true;
90         }
91         if (updated) {
92             invalidate();
93             setSize(_psize);
94             getParent().setSize(_psize);
95         }
96         return (infoflags & ALLBITS) == 0;
97     }
98
99     /**
100      * Adjusts the progress display to the specified percentage.
101      */
102     public void setProgress (int percent, long remaining)
103     {
104         boolean needsRepaint = false;
105
106         // maybe update the progress label
107         if (_progress != percent) {
108             _progress = percent;
109             if (!_ifc.hideProgressText) {
110                 String msg = MessageFormat.format(get("m.complete"), percent);
111                 _newplab = createLabel(msg, new Color(_ifc.progressText, true));
112             }
113             needsRepaint = true;
114         }
115
116         // maybe update the remaining label
117         if (remaining > 1) {
118             // skip this estimate if it's been less than a second since our last one came in
119             if (!_rthrottle.throttleOp()) {
120                 _remain[_ridx++%_remain.length] = remaining;
121             }
122
123             // smooth the remaining time by taking the trailing average of the last four values
124             remaining = 0;
125             int values = Math.min(_ridx, _remain.length);
126             for (int ii = 0; ii < values; ii++) {
127                 remaining += _remain[ii];
128             }
129             remaining /= values;
130
131             if (!_ifc.hideProgressText) {
132                 // now compute our display value
133                 int minutes = (int)(remaining / 60), seconds = (int)(remaining % 60);
134                 String remstr = minutes + ":" + ((seconds < 10) ? "0" : "") + seconds;
135                 String msg = MessageFormat.format(get("m.remain"), remstr);
136                 _newrlab = createLabel(msg, new Color(_ifc.statusText, true));
137             }
138             needsRepaint = true;
139
140         } else if (_rlabel != null || _newrlab != null) {
141             _rthrottle = new Throttle(1, 1000);
142             _ridx = 0;
143             _newrlab = _rlabel = null;
144             needsRepaint = true;
145         }
146
147         if (needsRepaint) {
148             repaint();
149         }
150     }
151
152     /**
153      * Displays the specified status string.
154      */
155     public void setStatus (String status, boolean displayError)
156     {
157         _status = xlate(status);
158         _displayError = displayError;
159         updateStatusLabel();
160     }
161
162     /**
163      * Stop the throbbing.
164      */
165     public void stopThrob ()
166     {
167         _timer.stop();
168         _statusDots = 3;
169         updateStatusLabel();
170     }
171
172     @Override
173     public void addNotify ()
174     {
175         super.addNotify();
176         _timer.start();
177     }
178
179     @Override
180     public void removeNotify ()
181     {
182         _timer.stop();
183         super.removeNotify();
184     }
185
186     // documentation inherited
187     @Override
188     public void paintComponent (Graphics g)
189     {
190         super.paintComponent(g);
191         Graphics2D gfx = (Graphics2D)g;
192
193         // attempt to draw a background image...
194         Image img;
195         if (_displayError) {
196             img = _bg.getErrorImage();
197         } else {
198             img = _bg.getImage(_progress);
199         }
200         if (img != null) {
201             gfx.drawImage(img, 0, 0, this);
202         }
203
204         Object oalias = SwingUtil.activateAntiAliasing(gfx);
205
206         // if we have new labels; lay them out
207         if (_newlab != null) {
208             _newlab.layout(gfx);
209             _label = _newlab;
210             _newlab = null;
211         }
212         if (_newplab != null) {
213             _newplab.layout(gfx);
214             _plabel = _newplab;
215             _newplab = null;
216         }
217         if (_newrlab != null) {
218             _newrlab.layout(gfx);
219             _rlabel = _newrlab;
220             _newrlab = null;
221         }
222
223         if (_barimg != null) {
224             gfx.setClip(_ifc.progress.x, _ifc.progress.y,
225                         _progress * _ifc.progress.width / 100,
226                         _ifc.progress.height);
227             gfx.drawImage(_barimg, _ifc.progress.x, _ifc.progress.y, null);
228             gfx.setClip(null);
229         } else {
230             gfx.setColor(new Color(_ifc.progressBar, true));
231             gfx.fillRect(_ifc.progress.x, _ifc.progress.y,
232                          _progress * _ifc.progress.width / 100,
233                          _ifc.progress.height);
234         }
235
236         if (_plabel != null) {
237             int xmarg = (_ifc.progress.width - _plabel.getSize().width)/2;
238             int ymarg = (_ifc.progress.height - _plabel.getSize().height)/2;
239             _plabel.render(gfx, _ifc.progress.x + xmarg, _ifc.progress.y + ymarg);
240         }
241
242         if (_label != null) {
243             _label.render(gfx, _ifc.status.x, getStatusY(_label));
244         }
245
246         if (_rlabel != null) {
247             // put the remaining label at the end of the status area. This could be dangerous
248             // but I think the only time we would display it is with small statuses.
249             int x = _ifc.status.x + _ifc.status.width - _rlabel.getSize().width;
250             _rlabel.render(gfx, x, getStatusY(_rlabel));
251         }
252
253         SwingUtil.restoreAntiAliasing(gfx, oalias);
254     }
255
256     // documentation inherited
257     @Override
258     public Dimension getPreferredSize ()
259     {
260         return _psize;
261     }
262
263     /**
264      * Update the status label.
265      */
266     protected void updateStatusLabel ()
267     {
268         String status = _status;
269         if (!_displayError) {
270             for (int ii = 0; ii < _statusDots; ii++) {
271                 status += " .";
272             }
273         }
274         _newlab = createLabel(status, new Color(_ifc.statusText, true));
275         // set the width of the label to the width specified
276         int width = _ifc.status.width;
277         if (width == 0) {
278             // unless we had trouble reading that width, in which case use the entire window
279             width = getWidth();
280         }
281         // but the window itself might not be initialized and have a width of 0
282         if (width > 0) {
283             _newlab.setTargetWidth(width);
284         }
285         repaint();
286     }
287
288     /**
289      * Get the y coordinate of a label in the status area.
290      */
291     protected int getStatusY (Label label)
292     {
293         // if the status region is higher than the progress region, we
294         // want to align the label with the bottom of its region
295         // rather than the top
296         if (_ifc.status.y > _ifc.progress.y) {
297             return _ifc.status.y;
298         }
299         return _ifc.status.y + (_ifc.status.height - label.getSize().height);
300     }
301
302     /**
303      * Create a label, taking care of adding the shadow if needed.
304      */
305     protected Label createLabel (String text, Color color)
306     {
307         Label label = new Label(text, color, FONT);
308         if (_ifc.textShadow != 0) {
309             label.setAlternateColor(new Color(_ifc.textShadow, true));
310             label.setStyle(LabelStyleConstants.SHADOW);
311         }
312         return label;
313     }
314
315     /** Used by {@link #setStatus}. */
316     protected String xlate (String compoundKey)
317     {
318         // to be more efficient about creating unnecessary objects, we
319         // do some checking before splitting
320         int tidx = compoundKey.indexOf('|');
321         if (tidx == -1) {
322             return get(compoundKey);
323
324         } else {
325             String key = compoundKey.substring(0, tidx);
326             String argstr = compoundKey.substring(tidx+1);
327             String[] args = argstr.split("\\|");
328             // unescape and translate the arguments
329             for (int i = 0; i < args.length; i++) {
330                 // if the argument is tainted, do no further translation
331                 // (it might contain |s or other fun stuff)
332                 if (MessageUtil.isTainted(args[i])) {
333                     args[i] = MessageUtil.unescape(MessageUtil.untaint(args[i]));
334                 } else {
335                     args[i] = xlate(MessageUtil.unescape(args[i]));
336                 }
337             }
338             return get(key, args);
339         }
340     }
341
342     /** Used by {@link #setStatus}. */
343     protected String get (String key, String[] args)
344     {
345         String msg = get(key);
346         if (msg != null) return MessageFormat.format(MessageUtil.escape(msg), (Object[])args);
347         return key + String.valueOf(Arrays.asList(args));
348     }
349
350     /** Used by {@link #setStatus}, and {@link #setProgress}. */
351     protected String get (String key)
352     {
353         // if we have no _msgs that means we're probably recovering from a
354         // failure to load the translation messages in the first place, so
355         // just give them their key back because it's probably an english
356         // string; whee!
357         if (_msgs == null) {
358             return key;
359         }
360
361         // if this string is tainted, we don't translate it, instead we
362         // simply remove the taint character and return it to the caller
363         if (MessageUtil.isTainted(key)) {
364             return MessageUtil.untaint(key);
365         }
366         try {
367             return _msgs.getString(key);
368         } catch (MissingResourceException mre) {
369             log.warning("Missing translation message '" + key + "'.");
370             return key;
371         }
372     }
373
374     protected Image _barimg;
375     protected RotatingBackgrounds _bg;
376     protected Dimension _psize;
377
378     protected ResourceBundle _msgs;
379
380     protected int _progress = -1;
381     protected String _status;
382     protected int _statusDots = 1;
383     protected boolean _displayError;
384     protected Label _label, _newlab;
385     protected Label _plabel, _newplab;
386     protected Label _rlabel, _newrlab;
387
388     protected UpdateInterface _ifc;
389     protected Timer _timer;
390
391     protected long[] _remain = new long[4];
392     protected int _ridx;
393     protected Throttle _rthrottle = new Throttle(1, 1000L);
394
395     protected static final Font FONT = new Font("SansSerif", Font.BOLD, 12);
396 }