2 // Getdown - application installer, patcher and launcher
3 // Copyright (C) 2004-2018 Getdown authors
4 // https://github.com/threerings/getdown/blob/master/LICENSE
6 package com.threerings.getdown.launcher;
9 import java.awt.Dimension;
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;
22 import javax.swing.JComponent;
23 import javax.swing.Timer;
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;
30 import com.threerings.getdown.data.Application;
31 import com.threerings.getdown.data.Application.UpdateInterface;
32 import com.threerings.getdown.data.Build;
33 import com.threerings.getdown.util.MessageUtil;
34 import com.threerings.getdown.util.Rectangle;
35 import com.threerings.getdown.util.StringUtil;
36 import static com.threerings.getdown.Log.log;
39 * Displays download and patching status.
41 public final class StatusPanel extends JComponent
42 implements ImageObserver
44 public StatusPanel (ResourceBundle msgs)
48 // Add a bit of "throbbing" to the display by updating the number of dots displayed after
49 // our status. This lets users know things are still working.
50 _timer = new Timer(1000,
51 new ActionListener() {
52 public void actionPerformed (ActionEvent event) {
53 if (_status != null && !_displayError) {
54 _statusDots = (_statusDots % 3) + 1; // 1, 2, 3, 1, 2, 3, etc.
61 public void init (UpdateInterface ifc, RotatingBackgrounds bg, Image barimg)
65 Image img = _bg.getImage(_progress);
66 int width = img == null ? -1 : img.getWidth(this);
67 int height = img == null ? -1 : img.getHeight(this);
68 if (width == -1 || height == -1) {
69 Rectangle bounds = ifc.progress.union(ifc.status);
70 // assume the x inset defines the frame padding; add it on the left, right, and bottom
71 _psize = new Dimension(bounds.x + bounds.width + bounds.x,
72 bounds.y + bounds.height + bounds.x);
74 _psize = new Dimension(width, height);
81 public boolean imageUpdate (Image img, int infoflags, int x, int y, int width, int height)
83 boolean updated = false;
84 if ((infoflags & WIDTH) != 0) {
88 if ((infoflags & HEIGHT) != 0) {
89 _psize.height = height;
95 getParent().setSize(_psize);
97 return (infoflags & ALLBITS) == 0;
101 * Adjusts the progress display to the specified percentage.
103 public void setProgress (int percent, long remaining)
105 boolean needsRepaint = false;
107 // maybe update the progress label
108 if (_progress != percent) {
110 if (_ifc != null && !_ifc.hideProgressText) {
111 String msg = MessageFormat.format(get("m.complete"), percent);
112 _newplab = createLabel(msg, new Color(_ifc.progressText, true));
117 // maybe update the remaining label
119 // skip this estimate if it's been less than a second since our last one came in
120 if (!_rthrottle.throttleOp()) {
121 _remain[_ridx++%_remain.length] = remaining;
124 // smooth the remaining time by taking the trailing average of the last four values
126 int values = Math.min(_ridx, _remain.length);
127 for (int ii = 0; ii < values; ii++) {
128 remaining += _remain[ii];
132 if (_ifc != null && !_ifc.hideProgressText) {
133 // now compute our display value
134 int minutes = (int)(remaining / 60), seconds = (int)(remaining % 60);
135 String remstr = minutes + ":" + ((seconds < 10) ? "0" : "") + seconds;
136 String msg = MessageFormat.format(get("m.remain"), remstr);
137 _newrlab = createLabel(msg, new Color(_ifc.statusText, true));
141 } else if (_rlabel != null || _newrlab != null) {
142 _rthrottle = new Throttle(1, 1000);
144 _newrlab = _rlabel = null;
154 * Displays the specified status string.
156 public void setStatus (String status, boolean displayError)
158 _status = xlate(status);
159 _displayError = displayError;
164 * Stop the throbbing.
166 public void stopThrob ()
174 public void addNotify ()
181 public void removeNotify ()
184 super.removeNotify();
187 // documentation inherited
189 public void paintComponent (Graphics g)
191 super.paintComponent(g);
192 Graphics2D gfx = (Graphics2D)g;
194 // attempt to draw a background image...
197 img = _bg.getErrorImage();
199 img = _bg.getImage(_progress);
202 gfx.drawImage(img, 0, 0, this);
205 Object oalias = SwingUtil.activateAntiAliasing(gfx);
207 // if we have new labels; lay them out
208 if (_newlab != null) {
213 if (_newplab != null) {
214 _newplab.layout(gfx);
218 if (_newrlab != null) {
219 _newrlab.layout(gfx);
224 if (_barimg != null) {
225 gfx.setClip(_ifc.progress.x, _ifc.progress.y,
226 _progress * _ifc.progress.width / 100,
227 _ifc.progress.height);
228 gfx.drawImage(_barimg, _ifc.progress.x, _ifc.progress.y, null);
231 gfx.setColor(new Color(_ifc.progressBar, true));
232 gfx.fillRect(_ifc.progress.x, _ifc.progress.y,
233 _progress * _ifc.progress.width / 100,
234 _ifc.progress.height);
237 if (_plabel != null) {
238 int xmarg = (_ifc.progress.width - _plabel.getSize().width)/2;
239 int ymarg = (_ifc.progress.height - _plabel.getSize().height)/2;
240 _plabel.render(gfx, _ifc.progress.x + xmarg, _ifc.progress.y + ymarg);
243 if (_label != null) {
244 _label.render(gfx, _ifc.status.x, getStatusY(_label));
247 if (_rlabel != null) {
248 // put the remaining label at the end of the status area. This could be dangerous
249 // but I think the only time we would display it is with small statuses.
250 int x = _ifc.status.x + _ifc.status.width - _rlabel.getSize().width;
251 _rlabel.render(gfx, x, getStatusY(_rlabel));
254 SwingUtil.restoreAntiAliasing(gfx, oalias);
257 // documentation inherited
259 public Dimension getPreferredSize ()
265 * Update the status label.
267 protected void updateStatusLabel ()
272 String status = _status;
273 if (!_displayError) {
274 for (int ii = 0; ii < _statusDots; ii++) {
279 StringBuilder labelText = new StringBuilder();
280 if (_ifc.displayVersion) {
281 labelText.append("launcher version: " + Build.version());
282 labelText.append("\n");
283 labelText.append("install4j version: " + Application.i4jVersion);
284 labelText.append("\n");
285 labelText.append("installer version: " + System.getProperty("installer_template_version"));
286 labelText.append("\n");
288 if (_ifc.displayAppbase) {
289 labelText.append("appbase: " + _appbase);
290 labelText.append("\n");
292 labelText.append(status);
294 _newlab = createLabel(labelText.toString(), new Color(_ifc.statusText, true));
295 // set the width of the label to the width specified
296 int width = _ifc.status.width;
298 // unless we had trouble reading that width, in which case use the entire window
301 // but the window itself might not be initialized and have a width of 0
303 _newlab.setTargetWidth(width);
309 * Get the y coordinate of a label in the status area.
311 protected int getStatusY (Label label)
313 // if the status region is higher than the progress region, we
314 // want to align the label with the bottom of its region
315 // rather than the top
316 if (_ifc.status.y > _ifc.progress.y) {
317 return _ifc.status.y;
319 return _ifc.status.y + (_ifc.status.height - label.getSize().height);
323 * Create a label, taking care of adding the shadow if needed.
325 protected Label createLabel (String text, Color color)
327 Label label = new Label(text, color, FONT);
328 if (_ifc.textShadow != 0) {
329 label.setAlternateColor(new Color(_ifc.textShadow, true));
330 label.setStyle(LabelStyleConstants.SHADOW);
335 /** Used by {@link #setStatus}. */
336 protected String xlate (String compoundKey)
338 // to be more efficient about creating unnecessary objects, we
339 // do some checking before splitting
340 int tidx = compoundKey.indexOf('|');
342 return get(compoundKey);
345 String key = compoundKey.substring(0, tidx);
346 String argstr = compoundKey.substring(tidx+1);
347 String[] args = argstr.split("\\|");
348 // unescape and translate the arguments
349 for (int i = 0; i < args.length; i++) {
350 // if the argument is tainted, do no further translation
351 // (it might contain |s or other fun stuff)
352 if (MessageUtil.isTainted(args[i])) {
353 args[i] = MessageUtil.unescape(MessageUtil.untaint(args[i]));
355 args[i] = xlate(MessageUtil.unescape(args[i]));
358 return get(key, args);
362 /** Used by {@link #setStatus}. */
363 protected String get (String key, String[] args)
365 String msg = get(key);
366 if (msg != null) return MessageFormat.format(MessageUtil.escape(msg), (Object[])args);
367 return key + String.valueOf(Arrays.asList(args));
370 /** Used by {@link #setStatus}, and {@link #setProgress}. */
371 protected String get (String key)
373 // if we have no _msgs that means we're probably recovering from a
374 // failure to load the translation messages in the first place, so
375 // just give them their key back because it's probably an english
381 // if this string is tainted, we don't translate it, instead we
382 // simply remove the taint character and return it to the caller
383 if (MessageUtil.isTainted(key)) {
384 return MessageUtil.untaint(key);
387 return _msgs.getString(key);
388 } catch (MissingResourceException mre) {
389 log.warning("Missing translation message '" + key + "'.");
394 public void setAppbase(String appbase) {
398 protected Image _barimg;
399 protected RotatingBackgrounds _bg;
400 protected Dimension _psize;
402 protected ResourceBundle _msgs;
404 protected int _progress = -1;
405 protected String _status;
406 protected int _statusDots = 1;
407 protected boolean _displayError;
408 protected Label _label, _newlab;
409 protected Label _plabel, _newplab;
410 protected Label _rlabel, _newrlab;
412 protected UpdateInterface _ifc;
413 protected Timer _timer;
415 protected long[] _remain = new long[4];
417 protected Throttle _rthrottle = new Throttle(1, 1000L);
419 protected static final Font FONT = new Font("SansSerif", Font.BOLD, 12);
421 public String _appbase;