// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
package net.sourceforge.jnlp.cache;
import static net.sourceforge.jnlp.runtime.Translator.R;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.Timer;
import javax.jnlp.*;
import net.sourceforge.jnlp.runtime.*;
/**
* Show the progress of downloads.
*
* @author Jon A. Maxwell (JAM) - initial author
* @version $Revision: 1.3 $
*/
public class DefaultDownloadIndicator implements DownloadIndicator {
// todo: rewrite this to cut down on size/complexity; smarter
// panels (JList, renderer) understand resources instead of
// nested panels and grid-bag mess.
// todo: fix bug where user closes download box and it
// never(?) reappears.
// todo: UI for user to cancel/restart download
// todo: this should be synchronized at some point but conflicts
// aren't very likely.
private static String downloading = R("CDownloading");
private static String complete = R("CComplete");
/** time to wait after completing but before window closes */
private static final int CLOSE_DELAY = 750;
/** the display window */
private static JFrame frame;
private static final Object frameMutex = new Object();
/** shared constraint */
static GridBagConstraints vertical;
static GridBagConstraints verticalIndent;
static {
vertical = new GridBagConstraints();
vertical.gridwidth = GridBagConstraints.REMAINDER;
vertical.weightx = 1.0;
vertical.fill = GridBagConstraints.HORIZONTAL;
vertical.anchor = GridBagConstraints.WEST;
verticalIndent = (GridBagConstraints) vertical.clone();
verticalIndent.insets = new Insets(0, 10, 3, 0);
}
/**
* Return the update rate.
*/
public int getUpdateRate() {
return 150; //ms
}
/**
* Return the initial delay before obtaining a listener.
*/
public int getInitialDelay() {
return 300; //ms
}
/**
* Return a download service listener that displays the progress
* in a shared download info window.
*
* @param app the downloading application, or null if N/A
* @param downloadName name identifying the download to the user
* @param resources initial urls to display (not required)
*/
public DownloadServiceListener getListener(ApplicationInstance app, String downloadName, URL resources[]) {
DownloadPanel result = new DownloadPanel(downloadName);
synchronized (frameMutex) {
if (frame == null) {
frame = new JFrame(downloading + "...");
frame.getContentPane().setLayout(new GridBagLayout());
}
if (resources != null)
for (int i = 0; i < resources.length; i++)
result.addProgressPanel(resources[i], null);
frame.getContentPane().add(result, vertical);
frame.pack();
if (!frame.isVisible()) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration());
Dimension screen = new Dimension(screenSize.width - insets.left,
screenSize.height - insets.top);
frame.setLocation(screen.width - frame.getWidth(),
screen.height - frame.getHeight());
}
frame.setVisible(true);
return result;
}
}
/**
* Remove a download service listener that was obtained by
* calling the getDownloadListener method from the shared
* download info window.
*/
public void disposeListener(final DownloadServiceListener listener) {
if (!(listener instanceof DownloadPanel))
return;
ActionListener hider = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
synchronized(frameMutex) {
frame.getContentPane().remove((DownloadPanel) listener);
frame.pack();
if (frame.getContentPane().getComponentCount() == 0) {
frame.setVisible(false);
frame.dispose();
frame = null;
}
}
}
};
Timer timer = new Timer(CLOSE_DELAY, hider);
timer.setRepeats(false);
timer.start();
}
/**
* Groups the url progress in a panel.
*/
static class DownloadPanel extends JPanel implements DownloadServiceListener {
/** the download name */
private String downloadName;
/** Downloading part: */
private JLabel header = new JLabel();
/** list of URLs being downloaded */
private List urls = new ArrayList();
/** list of ProgressPanels */
private List panels = new ArrayList();
/**
* Create a new download panel for with the specified download
* name.
*/
protected DownloadPanel(String downloadName) {
setLayout(new GridBagLayout());
this.downloadName = downloadName;
this.add(header, vertical);
header.setFont(header.getFont().deriveFont(Font.BOLD));
setOverallPercent(0);
}
/**
* Add a ProgressPanel for a URL.
*/
protected void addProgressPanel(URL url, String version) {
if (!urls.contains(url)) {
ProgressPanel panel = new ProgressPanel(url, version);
add(panel, verticalIndent);
synchronized (frameMutex) {
frame.pack();
}
urls.add(url);
panels.add(panel);
}
}
/**
* Update the download progress of a url.
*/
protected void update(final URL url, final String version,
final long readSoFar, final long total,
final int overallPercent) {
Runnable r = new Runnable() {
public void run() {
if (!urls.contains(url))
addProgressPanel(url, version);
setOverallPercent(overallPercent);
ProgressPanel panel = panels.get(urls.indexOf(url));
panel.setProgress(readSoFar, total);
panel.repaint();
}
};
SwingUtilities.invokeLater(r);
}
/**
* Sets the overall percent completed.
*/
public void setOverallPercent(int percent) {
// don't get whole string from resource and sub in
// values because it'll be doing a MessageFormat for
// each update.
header.setText(downloading + " " + downloadName + ": " + percent + "% " + complete + ".");
}
/**
* Called when a download failed.
*/
public void downloadFailed(URL url, String version) {
update(url, version, -1, -1, -1);
}
/**
* Called when a download has progressed.
*/
public void progress(URL url, String version, long readSoFar, long total, int overallPercent) {
update(url, version, readSoFar, total, overallPercent);
}
/**
* Called when an archive is patched.
*/
public void upgradingArchive(URL url, String version, int patchPercent, int overallPercent) {
update(url, version, patchPercent, 100, overallPercent);
}
/**
* Called when a download is being validated.
*/
public void validating(URL url, String version, long entry, long total, int overallPercent) {
update(url, version, entry, total, overallPercent);
}
};
/**
* A progress bar with the URL next to it.
*/
static class ProgressPanel extends JPanel {
private JPanel bar = new JPanel();
private long total;
private long readSoFar;
ProgressPanel(URL url, String version) {
JLabel location = new JLabel(" " + url.getHost() + "/" + url.getFile());
bar.setMinimumSize(new Dimension(80, 15));
bar.setPreferredSize(new Dimension(80, 15));
bar.setOpaque(false);
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.weightx = 0.0;
gbc.fill = GridBagConstraints.NONE;
gbc.gridwidth = GridBagConstraints.RELATIVE;
add(bar, gbc);
gbc.insets = new Insets(0, 3, 0, 0);
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.WEST;
add(location, gbc);
}
public void setProgress(long readSoFar, long total) {
this.readSoFar = readSoFar;
this.total = total;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x = bar.getX();
int y = bar.getY();
int h = bar.getHeight();
int w = bar.getWidth();
if (readSoFar <= 0 || total <= 0) {
// make barber pole
} else {
double progress = (double) readSoFar / (double) total;
int divide = (int) (w * progress);
g.setColor(Color.white);
g.fillRect(x, y, w, h);
g.setColor(Color.blue);
g.fillRect(x + 1, y + 1, divide - 1, h - 1);
}
}
};
}