/*
* Copyright (C) 2006-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (C) 2011 Peransin Nicolas. All rights reserved.
* Use is subject to license terms.
*/
package org.mypsycho.swing.app.task;
import static org.mypsycho.swing.app.utils.SwingHelper.findRootPaneContainer;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
/**
* Pane for blocking dialog.
* <p>
* FIXME: locale propagation is ignored
* </p>
*
* @author Peransin Nicolas
*/
public class InputBlockerPane extends JOptionPane {
private static final long serialVersionUID = -4641857212581584090L;
public static final String ON_ESCAPE_ACTION_KEY = "onEscape";
Task<?, ?> task;
String progessText = "";
String defaultMessage = ""; // overridden by task.message
String title = ""; // overridden by task.title
JButton cancelButton = null; // injected
JTextArea label = new JTextArea(); // Updated by task.message
final JProgressBar progressBar = new JProgressBar();
private static final Object[] NO_CANCEL_OPTION = new Object[0];
private static final KeyStroke ESCAPE_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
public InputBlockerPane(Task<?, ?> followed) {
setName("blockingPane");
task = followed;
/*
* If the task can be canceled, then add the cancel
* button. Otherwise clear the default OK button.
*/
if (task.getUserCancellable()) {
cancelButton = new JButton("Cancel"); // injected
cancelButton.setName("cancel");
ActionListener doCancelTask = new ActionListener() {
@Override
public void actionPerformed(ActionEvent ignore) {
task.cancel(true);
}
};
cancelButton.addActionListener(doCancelTask);
setOptions(new Object[] { cancelButton });
} else {
setOptions(NO_CANCEL_OPTION); // no OK button
}
InputMap inputs = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputs.put(ESCAPE_KEY, ON_ESCAPE_ACTION_KEY);
final List<String> boundProps = Arrays.asList("background", "font");
addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
if (boundProps.contains(e.getPropertyName())) {
installLabel();
}
}
});
installLabel();
JPanel panel = new JPanel(new BorderLayout());
panel.add(label, BorderLayout.CENTER);
progressBar.setIndeterminate(true);
PropertyChangeListener taskPCL = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
if (Task.PROGRESS_PROP.equals(e.getPropertyName())) {
progressBar.setIndeterminate(false);
updateProgress();
} else if (Task.MESSAGE_PROP.equals(e.getPropertyName())) {
label.setText((String) e.getNewValue());
}
}
};
task.addPropertyChangeListener(taskPCL);
label.setText(task.getMessage());
panel.add(progressBar, BorderLayout.PAGE_END);
/*
* The initial value of the progressBar string is the format.
* We save the format string in a client property. The format
* String will be applied four values (see below). The default
* format String is in resources/Application.properties, it's:
* "%02d:%02d, %02d:%02d remaining"
* FIXED: BSAF-12
*/
progressBar.setString("");
setMessage(panel);
}
private void installLabel() {
label.setFont(getFont());
int lh = label.getFontMetrics(getFont()).getHeight();
Insets margin = new Insets(0, 0, lh, 24); // top left bottom right
label.setMargin(margin);
label.setEditable(false);
label.setWrapStyleWord(true);
label.setBackground(getBackground());
}
/* Creates a dialog whose visuals are initialized from the
* following Task resources:
* inputBlocker.dialog.title
* inputBlocker.dialog.icon
* inputBlocker.dialog.defaultMessage
* inputBlocker.dialog.cancel.text
* inputBlocker.dialog.cancel.icon
* inputBlocker.dialog.progressBar.stringPainted
*
* If the Task has an Action then use the actionName as a prefix
* and look up the resources again, in the action's ResourceMap
* (that's the @Action's ApplicationActionMap ResourceMap really):
* <action path>.task.inputBlocker.dialog.title
* <action path>.task.inputBlocker.dialog.icon
* <action path>.task.inputBlocker.dialog.defaultMessage
* <action path>.task.inputBlocker.dialog.cancel.text
* <action path>.task.inputBlocker.dialog.cancel.icon
* <action path>.task.inputBlocker.dialog.progressBar.stringPainted
*/
public JDialog createDialog(Component dialogOwner) {
String dialogTitle = (getTitle() != null) ? getTitle() : task.getTitle();
final JDialog dialog =
createDialog((Component) findRootPaneContainer(dialogOwner), dialogTitle);
dialog.setModal(true);
dialog.setName("BlockingDialog");
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
WindowListener dialogCloseListener = new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (task.getUserCancellable()) {
task.cancel(true);
dialog.setVisible(false);
}
}
};
dialog.addWindowListener(dialogCloseListener);
dialog.pack();
return dialog;
}
private Object[] messageArgs = null; // cache : update is in EDT, no synchro issue
private void updateProgress() {
if (progressBar.isIndeterminate()) {
return;
}
progressBar.setValue(task.getProgress());
if (!progressBar.isStringPainted()) {
return;
}
if ((progressBar.getValue() <= 0) || (progessText == null)) {
progressBar.setString("");
} else {
messageArgs = getMessageArgs(messageArgs);
progressBar.setString(MessageFormat.format(progessText, messageArgs));
}
}
protected Object[] getMessageArgs(Object[] cache) {
if (cache == null) {
cache = new Object[5];
}
long pctComplete = task.getProgress();
long durSeconds = task.getExecutionDuration(TimeUnit.SECONDS);
long remSeconds = Math.round(durSeconds * 100. / pctComplete) - durSeconds;
// Dirty approach for printable duration
cache[0] = durSeconds / 60; // elapsed duration
cache[1] = durSeconds % 60;
cache[2] = remSeconds / 60; // Remaining duration
cache[3] = remSeconds % 60;
cache[4] = pctComplete; // % complete
return cache;
}
/**
* Returns the cancelButton.
*
* @return the cancelButton
*/
public JButton getCancel() {
return cancelButton;
}
/**
* Returns the progressBar.
*
* @return the progressBar
*/
public JProgressBar getProgressBar() {
return progressBar;
}
/**
* Returns the progessText.
*
* @return the progessText
*/
public String getProgessText() {
return progessText;
}
/**
* Sets the progessText.
*
* @param progessText the progessText to set
*/
public void setProgessText(String progessText) {
this.progessText = progessText;
updateProgress();
}
/**
* Returns the defaultLabel.
*
* @return the defaultLabel
*/
public String getDefaultMessage() {
return defaultMessage;
}
/**
* Sets the defaultLabel.
*
* @param defaultLabel the defaultLabel to set
*/
public void setDefaultMessage(String defaultLabel) {
this.defaultMessage = defaultLabel;
if (label.getDocument().getLength() == 0) {
label.setText(defaultMessage);
}
}
/**
* Returns the title.
*
* @return the title
*/
public String getTitle() {
return title;
}
/**
* Sets the title.
*
* @param title the title to set
*/
public void setTitle(String title) {
this.title = title;
// No update, injection is performed before dialog creation
}
}