/*
* ActiveTaskViewImpl.java
*
* Copyright (C) 2010 Leo Osvald <leo.osvald@gmail.com>
*
* This file is part of SGLJ.
*
* SGLJ 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 3 of the License, or
* (at your option) any later version.
*
* SGLJ 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, see <http://www.gnu.org/licenses/>.
*/
package org.sglj.swing.task;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.lang.ref.WeakReference;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import org.sglj.swing.task.SingleTaskModel.SingleTaskModelListener;
import org.sglj.task.DeterminateTask;
import org.sglj.task.Task;
import org.sglj.task.TaskEvent;
/**
* This implementation of interface {@link SingleTaskView} displays
* active tasks such that only one is displayed at a time, but it
* in a cyclic order. The change of currently displayed task
* occurs at fixed time periods, and can be adjusted through
* {@link #setDisplayDuration(int)} method.
*
* @author Leo Osvald
* @version 1.03
*/
public class ActiveTaskViewImpl extends JPanel
implements SingleTaskView, SingleTaskModelListener {
private SingleTaskModel model;
private NameTextProvider nameTextCreator;
private StatusTextProvider statusTextCreator;
private Task activeTask;
private Queue<WeakReference<Task>> displayCycle = new LinkedList<WeakReference<Task>>();
private Timer timer;
private JLabel nameLabel = new JLabel();
private JProgressBar progressBar;
private JLabel statusLabel = new JLabel();
private JPanel nameStatusPanel;
private final JSeparator nameStatusSeparator = new JSeparator(SwingConstants.VERTICAL);
private final Component nameStatusGlue = Box.createGlue();
private boolean taskNameDisplayed = true;
private boolean taskProgressDisplayed = true;
private boolean taskStatusDisplayed = true;
private int displayDuration = DEFAULT_DISPLAY_DURATION;
/**
* Default width of the progress bar
*/
public static final int DEFAULT_PROGRESS_BAR_WIDTH = 50;
/** minimum active task display duration in ms */
public static final int MIN_DISPLAY_DURATION = 500;
/** default active task display duration in ms */
private static final int DEFAULT_DISPLAY_DURATION = 2000;
private static final int PB_MAX_VAL = 1000;
private static final long serialVersionUID = 1L;
public ActiveTaskViewImpl(NameTextProvider nameTextCreator,
StatusTextProvider statusTextCreator, int progressBarWidth) {
setNameProvider(nameTextCreator);
setStatusTextProvider(statusTextCreator);
this.setLayout(new BorderLayout());
FontMetrics fm = getFontMetrics(getFont());
Dimension dim = new Dimension(0, fm.getHeight()+fm.getDescent());
nameLabel.setMinimumSize(dim);
nameLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
statusLabel.setMinimumSize(dim);
statusLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
progressBar = new JProgressBar(0, PB_MAX_VAL);
progressBar.setPreferredSize(new Dimension(progressBarWidth, 0));
progressBar.setVisible(false);
nameStatusPanel = new JPanel();
nameStatusPanel.setLayout(new BoxLayout(nameStatusPanel, BoxLayout.X_AXIS));
updateNameStatusPanel();
this.add(nameStatusPanel, BorderLayout.CENTER);
this.add(progressBar, BorderLayout.EAST);
timer = new Timer(DEFAULT_DISPLAY_DURATION, new ActionListener() {
// int cnt = 0;
@Override
public void actionPerformed(ActionEvent e) {
if(!displayCycle.isEmpty()) {
// System.out.println("Displaying next"+(cnt++));
displayNext();
}
}
});
timer.setInitialDelay(0);
//ensure that timer is not running when component is hidden, and vice-versa
addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent e) {
timer.start();
}
@Override
public void componentHidden(ComponentEvent e) {
timer.stop();
}
});
}
public ActiveTaskViewImpl(NameTextProvider nameTextCreator,
StatusTextProvider statusTextCreator) {
this(nameTextCreator, statusTextCreator, DEFAULT_PROGRESS_BAR_WIDTH);
}
@Override
public SingleTaskModel getModel() {
return model;
}
@Override
public void setModel(SingleTaskModel model) {
timer.stop();
displayCycle.clear();
//disconnect this view from old model
if(this.model != null)
this.model.removeSingleTaskModelListener(this);
//set new model
this.model = model;
model.addSingleTaskModelListener(this);
if(model.getTask() != null && model.getTask().getState()
== Task.TaskState.STARTED) {
displayCycle.add(new WeakReference<Task>(model.getTask()));
// System.out.println("Added to display cycle task: "+model.getTask().name());
// System.out.println("Task by ref: "+displayCycle.peek().get().name());
}
else {
// System.out.println("setModel: is task null? " + (model.getTask() == null));
}
timer.start();
}
@Override
public void taskStateChanged(TaskEvent e) {
Task task = e.getTask();
if(task == null)
return;
// System.out.println("ActiveTaskViewImpl - STATE CHANGED");
// System.out.println("New state is: "+task.getState().name());
//if a task just started, add it to display cycle
if(task.getState() == Task.TaskState.STARTED) {
displayCycle.add(new WeakReference<Task>(task));
//if there was no task in the display cycle, display it immediately
if(displayCycle.size() == 1)
timer.restart();
}
//if it is active and is not active, display next (it will be removed)
else if(task == activeTask) {
// System.out.println("COMPLETED OR CANCELLED");
displayNext();
}
}
@Override
public void taskChanged(TaskEvent e) {
Task task = e.getTask();
if(task == activeTask
|| activeTask == null && task != null)
updateAll(task);
}
@Override
public void taskUpdated(TaskEvent e) {
Task task = e.getTask();
if(task == null)
return ;
if(task == activeTask || activeTask == null) {
if(task.getState() == Task.TaskState.STARTED) {
// System.out.println("Updating ActiveTaskViewImpl: task is null?"
// + (task == null));
updateAll(task);
}
}
}
@Override
public void taskStatusChanged(TaskEvent e) {
if(!taskStatusDisplayed)
return ;
Task task = e.getTask();
if(task == null)
return ;
if(task == activeTask || activeTask == null)
updateStatus(task);
}
public Task getDislayedTask() {
return activeTask;
}
/**
* Sets active task display duration. This only matters when there are
* more than one active tasks; in that case, they are displayed
* one at a time, in cyclic order, and each one is displayed exactly
* <code>displayDuration</code> milliseconds.
* @param displayDuration time period in ms
* @throws IndexOutOfBoundsException if specified period is too short
* - see {@link #MIN_DISPLAY_DURATION}
*/
public void setDisplayDuration(int displayDuration)
throws IndexOutOfBoundsException {
//ensure that it meets restrictions
if(displayDuration < MIN_DISPLAY_DURATION)
throw new IndexOutOfBoundsException("Display duration too short.");
//set the new display duration
this.displayDuration = displayDuration;
timer.setDelay(displayDuration);
}
public int getDisplayDuration() {
return displayDuration;
}
@Override
public boolean isTaskNameDisplayed() {
return taskNameDisplayed;
}
@Override
public boolean isTaskProgressDisplayed() {
return taskNameDisplayed;
}
@Override
public boolean isTaskStatusDisplayed() {
return taskStatusDisplayed;
}
@Override
public void setTaskNameDisplayed(boolean enabled) {
boolean hasChanged = this.taskNameDisplayed ^ enabled;
this.taskNameDisplayed = enabled;
if(hasChanged)
updateNameStatusPanel();
}
@Override
public void setTaskProgressDisplayed(boolean enabled) {
// System.out.println("Set progress displayed("+enabled+")");
boolean hasChanged = this.taskProgressDisplayed ^ enabled;
this.taskProgressDisplayed = enabled;
if(hasChanged)
updateProgressDisplayed();
}
@Override
public void setTaskStatusDisplayed(boolean enabled) {
boolean hasChanged = this.taskStatusDisplayed ^ enabled;
this.taskStatusDisplayed = enabled;
if(hasChanged)
updateNameStatusPanel();
}
@Override
public NameTextProvider getNameTextProvider() {
return nameTextCreator;
}
@Override
public StatusTextProvider getStatusTextProvider() {
return statusTextCreator;
}
@Override
public void setStatusTextProvider(StatusTextProvider statusTextCreator) {
this.statusTextCreator = statusTextCreator;
}
@Override
public void setNameProvider(NameTextProvider nameTextCreator) {
this.nameTextCreator = nameTextCreator;
}
private void updateNameStatusPanel() {
nameStatusPanel.remove(nameLabel);
nameStatusPanel.remove(nameStatusSeparator);
nameStatusPanel.remove(statusLabel);
nameStatusPanel.remove(nameStatusGlue);
if(taskNameDisplayed) {
nameStatusPanel.add(nameLabel);
if(taskStatusDisplayed)
nameStatusPanel.add(nameStatusSeparator);
}
if(taskStatusDisplayed)
nameStatusPanel.add(statusLabel);
nameStatusPanel.add(nameStatusGlue);
this.validate();
}
private void updateProgressDisplayed() {
this.remove(progressBar);
if(taskProgressDisplayed) {
this.add(progressBar, BorderLayout.EAST);
// System.out.println("Added progressbar");
// for(Component c : this.getComponents())
// System.out.println(c);
}
this.validate();
}
void printTasks() {
int size = displayCycle.size();
System.out.println("---Printing "+size+" tasks");
for(int i = 0; i < size; ++i) {
WeakReference<Task> ref = displayCycle.poll();
Task t = ref.get();
System.out.printf("Task#%d: %s %d\n", i, (t != null ? t.name() : "-"),
(ref.isEnqueued() ? 1 : 0));
displayCycle.add(ref);
}
System.out.println();
}
private void displayNext() {
//cycle until there are actually references to running tasks
Task task = null;
while(!displayCycle.isEmpty()) {
// printTasks();
WeakReference<Task> front = displayCycle.poll();
if(!front.isEnqueued()) //if task is still alive
displayCycle.add(front); //rotate list
//keep cycling if task is not alive
if(displayCycle.peek().isEnqueued())
continue;
task = displayCycle.peek().get();
if(task != null && task.getState() == Task.TaskState.STARTED)
break;
//if task is somehow null or is not started yet, remove it
displayCycle.remove();
}
if(displayCycle.isEmpty())
task = null;
setActive(task);
}
private void setActive(Task task) {
// System.out.println("SETTING ACTIVE: "+task);
activeTask = task;
updateAll(task);
}
private void updateProgress(Task task) {
if(task == null) {
progressBar.setVisible(false);
return ;
}
else if(task == activeTask) {
if(task instanceof DeterminateTask) {
progressBar.setValue((int)(((DeterminateTask)task)
.getProgress()*PB_MAX_VAL));
progressBar.setIndeterminate(false);
progressBar.setStringPainted(true);
}
else {
progressBar.setString(null);
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
}
progressBar.setVisible(true);
}
}
private void updateName(Task task) {
if(task != null)
nameLabel.setText(task.name() != null ? task.name() : "");
else
nameLabel.setText(null);
}
private void updateStatus(Task task) {
if(task == null)
statusLabel.setText(null);
else if(task.getStatus() != null) {
String text = statusTextCreator.statusDescription(task);
statusLabel.setText(text != null ? text : "");
}
}
private void updateState(Task task) {
if(task != null && task.getState() != Task.TaskState.NOT_STARTED) {
progressBar.setValue(0);
progressBar.setVisible(true);
}
else {
progressBar.setVisible(false);
}
}
private void updateAll(Task task) {
updateState(task);
if(taskNameDisplayed)
updateName(task);
if(taskStatusDisplayed)
updateStatus(task);
if(taskProgressDisplayed)
updateProgress(task);
}
}