/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.job.progress;
import com.mucommander.job.FileJob;
import com.mucommander.job.FileJobListener;
import javax.swing.*;
import javax.swing.event.EventListenerList;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
/**
* A class that monitors jobs progress.
* @author Mariusz Jakubowski
*
*/
public class JobProgressMonitor implements FileJobListener {
/** Controls how often should current file label be refreshed (in ms) */
private final static int CURRENT_FILE_LABEL_REFRESH_RATE = 100;
/** Controls how often should progress information be refreshed */
private final static int MAIN_REFRESH_RATE = 10;
/** Time after which remove finished job from a monitor */
private final static int FINISHED_JOB_REMOVE_TIME = 1500;
/** Timer used to monitor jobs progress */
private Timer progressTimer;
/** List of listeners */
private EventListenerList listenerList = new EventListenerList();
/** A list of monitored jobs. */
private List<FileJob> jobs = new ArrayList<FileJob>();
/** An instance of this class */
private static final JobProgressMonitor instance = new JobProgressMonitor();
/**
* Creates a new JobProgressMonitor instance.
*/
private JobProgressMonitor() {
JobProgressTimer timerListener = new JobProgressTimer();
progressTimer = new Timer(CURRENT_FILE_LABEL_REFRESH_RATE, timerListener);
}
/**
* Returns the instance of JobProgressMonitor.
* @return the instance of JobProgressMonitor.
*/
public static JobProgressMonitor getInstance() {
return instance;
}
/**
* Adds a listener to the list that's notified each time a job
* progress is updated.
*
* @param l the JobProgressListener
*/
public void addJobProgressListener(JobProgressListener l) {
listenerList.add(JobProgressListener.class, l);
}
/**
* Removes a listener from the list that's notified each time job
* progress is updated.
*
* @param l the JobProgressListener
*/
public void removeJobProgressListener(JobProgressListener l) {
listenerList.remove(JobProgressListener.class, l);
}
/**
* Forwards the progress notification event to all
* <code>JobProgressListeners</code> that registered
* themselves as listeners.
* @param source a job for which the progress has been updated
* @param fullUpdate if false only file label has been updated
*
* @see #addJobProgressListener
* @see JobProgressListener#jobProgress
*/
private void fireJobProgress(FileJob source, boolean fullUpdate) {
int idx = jobs.indexOf(source);
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
((JobProgressListener)listeners[i+1]).jobProgress(source, idx, fullUpdate);
}
}
/**
* Forwards the job added notification event to all
* <code>JobProgressListeners</code> that registered
* themselves as listeners.
* @param source an added job
* @param idx index of a job in a list
*
* @see #addJobProgressListener
* @see JobProgressListener#jobAdded(FileJob, int)
*/
private void fireJobAdded(FileJob source, int idx) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
((JobProgressListener)listeners[i+1]).jobAdded(source, idx);
}
}
/**
* Forwards the job removed notification event to all
* <code>JobProgressListeners</code> that registered
* themselves as listeners.
* @param source a removed job
* @param idx index of a job in a list
*
* @see #addJobProgressListener
* @see JobProgressListener#jobRemoved(FileJob, int)
*/
private void fireJobRemoved(FileJob source, int idx) {
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length-2; i>=0; i-=2) {
((JobProgressListener)listeners[i+1]).jobRemoved(source, idx);
}
}
/**
* Adds a new job to the list of monitored jobs.
* This method is executed in Swing Thread (EDT).
* After adding a new job a {@link JobProgressListener#jobAdded(FileJob, int)}
* event is fired.
* @param job a job to be added
*/
public void addJob(final FileJob job) {
// ensure that this method is called in EDT
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
addJob(job);
}
});
}
jobs.add(job);
int idx = jobs.size() - 1;
fireJobAdded(job, idx);
if (!progressTimer.isRunning()) {
progressTimer.start();
}
job.addFileJobListener(this);
}
/**
* Removes a job from a list of monitored jobs.
* This method is executed in Swing Thread (EDT).
* After adding a new job a {@link JobProgressListener#jobRemoved(FileJob, int)}
* event is fired.
* @param job a job to be removed
*/
public void removeJob(final FileJob job) {
// ensure that this method is called in EDT
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
removeJob(job);
}
});
}
int idx = jobs.indexOf(job);
if (idx != -1) {
jobs.remove(idx);
}
if (jobs.isEmpty()) {
progressTimer.stop();
}
fireJobRemoved(job, idx);
job.removeFileJobListener(this);
}
/**
* Returns number of monitored jobs.
* @return number of monitored jobs.
*/
public int getJobCount() {
return jobs.size();
}
/**
* Returns a progress of a job with specified index.
* @param rowIndex an index of a job
* @return a progress information or null if job doesn't exists
*/
public JobProgress getJobProgres(int rowIndex) {
if (rowIndex < jobs.size()) {
FileJob job = jobs.get(rowIndex);
return job.getJobProgress();
}
return null;
}
/**
* A {@link FileJobListener} implementation.
* Removes a finished job after a small delay.
*/
public void jobStateChanged(final FileJob source, int oldState, int newState) {
if (newState==FileJob.FINISHED || newState==FileJob.INTERRUPTED) {
ActionListener jobToRemove = new ActionListener() {
public void actionPerformed(ActionEvent e) {
removeJob(source);
}
};
Timer timer = new Timer(FINISHED_JOB_REMOVE_TIME, jobToRemove);
timer.setRepeats(false);
timer.start();
}
}
/**
*
* This class implements a listener for a job progress timer.
*
*/
private class JobProgressTimer implements ActionListener {
/** a loop index indicating if this refresh is partial (label only) or full */
private int loopCount = 0;
public void actionPerformed(ActionEvent e) {
loopCount++;
boolean fullUpdate;
if (loopCount >= MAIN_REFRESH_RATE) {
fullUpdate = true;
loopCount = 0;
} else {
fullUpdate = false;
}
// for each job calculate new progress and notify listeners
for(FileJob job : jobs) {
boolean updateFullUI;
JobProgress jobProgress = job.getJobProgress();
updateFullUI = jobProgress.calcJobProgress(fullUpdate);
fireJobProgress(job, updateFullUI);
}
}
}
}