org.jdesktop.application.DefaultInputBlocker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tink-app Show documentation
Show all versions of tink-app Show documentation
Template based HTML formatter tool for small web sites.
The newest version!
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. Use is
* subject to license terms.
*/
package org.jdesktop.application;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.RootPaneContainer;
import javax.swing.Timer;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
final class DefaultInputBlocker extends Task.InputBlocker {
private static final Logger logger = Logger.getLogger(DefaultInputBlocker.class.getName());
private JDialog modalDialog = null;
DefaultInputBlocker(Task task, Task.BlockingScope scope, Object target, ApplicationAction action) {
super(task, scope, target, action);
}
private void setActionTargetBlocked(boolean f) {
javax.swing.Action action = (javax.swing.Action)getTarget();
action.setEnabled(!f);
}
private void setComponentTargetBlocked(boolean f) {
Component c = (Component)getTarget();
c.setEnabled(!f);
// Note: can't set the cursor on a disabled component
}
/* Accumulates a list of all of the descendants of root whose name
* begins with "BlockingDialog"
*/
private void blockingDialogComponents(Component root, List rv) {
String rootName = root.getName();
if ((rootName != null) && rootName.startsWith("BlockingDialog")) {
rv.add(root);
}
if (root instanceof Container) {
for(Component child : ((Container)root).getComponents()) {
blockingDialogComponents(child, rv);
}
}
}
private List blockingDialogComponents(Component root) {
List rv = new ArrayList();
blockingDialogComponents(root, rv);
return rv;
}
/* Inject resources from both the Task's ResourceMap and the
* ApplicationAction's ResourceMap. We add the action's name
* prefix to all of the components before the second step.
*/
private void injectBlockingDialogComponents(Component root) {
ResourceMap taskResourceMap = getTask().getResourceMap();
if (taskResourceMap != null) {
taskResourceMap.injectComponents(root);
}
ApplicationAction action = getAction();
if (action != null) {
ResourceMap actionResourceMap = action.getResourceMap();
String actionName = action.getName();
for(Component c : blockingDialogComponents(root)) {
c.setName(actionName + "." + c.getName());
}
actionResourceMap.injectComponents(root);
}
}
/* Creates a dialog whose visuals are initialized from the
* following Task resources:
* BlockingDialog.title
* BlockingDialog.optionPane.icon
* BlockingDialog.optionPane.message
* BlockingDialog.cancelButton.text
* BlockingDialog.cancelButton.icon
* BlockingDialog.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):
* actionName.BlockingDialog.title
* actionName.BlockingDialog.optionPane.icon
* actionName.BlockingDialog.optionPane.message
* actionName.BlockingDialog.cancelButton.text
* actionName.BlockingDialog.cancelButton.icon
* actionName.BlockingDialog.progressBar.stringPainted
*/
private JDialog createBlockingDialog() {
JOptionPane optionPane = new JOptionPane();
/* If the task can be canceled, then add the cancel
* button. Otherwise clear the default OK button.
*/
if (getTask().getUserCanCancel()) {
JButton cancelButton = new JButton();
cancelButton.setName("BlockingDialog.cancelButton");
ActionListener doCancelTask = new ActionListener() {
public void actionPerformed(ActionEvent ignore) {
getTask().cancel(true);
}
};
cancelButton.addActionListener(doCancelTask);
optionPane.setOptions(new Object[]{cancelButton});
}
else {
optionPane.setOptions(new Object[]{}); // no OK button
}
/* Create the JDialog. If the task can be canceled, then
* map closing the dialog window to canceling the task.
*/
Component dialogOwner = (Component)getTarget();
String taskTitle = getTask().getTitle();
String dialogTitle = (taskTitle == null) ? "BlockingDialog" : taskTitle;
final JDialog dialog = optionPane.createDialog(dialogOwner, dialogTitle);
dialog.setModal(true);
dialog.setName("BlockingDialog");
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
WindowListener dialogCloseListener = new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if (getTask().getUserCanCancel()) {
getTask().cancel(true);
dialog.setVisible(false);
}
}
};
dialog.addWindowListener(dialogCloseListener);
optionPane.setName("BlockingDialog.optionPane");
injectBlockingDialogComponents(dialog);
/* Reset the JOptionPane's message property after injecting
* an initial value for the message string.
*/
recreateOptionPaneMessage(optionPane);
dialog.pack();
return dialog;
}
/* Replace the default message panel with one that where the
* message text can be selected and that includes a status bar for
* task progress. We inject resources here because the
* JOptionPane#setMessage() doesn't add the panel to the JOptionPane
* immediately.
*/
private void recreateOptionPaneMessage(JOptionPane optionPane) {
Object message = optionPane.getMessage();
if (message instanceof String) {
Font font = optionPane.getFont();
final JTextArea textArea = new JTextArea((String)message);
textArea.setFont(font);
int lh = textArea.getFontMetrics(font).getHeight();
Insets margin = new Insets(0, 0, lh, 24); // top left bottom right
textArea.setMargin(margin);
textArea.setEditable(false);
textArea.setWrapStyleWord(true);
textArea.setBackground(optionPane.getBackground());
JPanel panel = new JPanel(new BorderLayout());
panel.add(textArea, BorderLayout.CENTER);
final JProgressBar progressBar = new JProgressBar();
progressBar.setName("BlockingDialog.progressBar");
progressBar.setIndeterminate(true);
PropertyChangeListener taskPCL = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if ("progress".equals(e.getPropertyName())) {
progressBar.setIndeterminate(false);
progressBar.setValue((Integer)e.getNewValue());
updateStatusBarString(progressBar);
}
else if ("message".equals(e.getPropertyName())) {
textArea.setText((String)e.getNewValue());
}
}
};
getTask().addPropertyChangeListener(taskPCL);
panel.add(progressBar, BorderLayout.SOUTH);
injectBlockingDialogComponents(panel);
optionPane.setMessage(panel);
}
}
private void updateStatusBarString(JProgressBar progressBar) {
if (!progressBar.isStringPainted()) {
return;
}
/* 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"
*/
String key = "progressBarStringFormat";
if (progressBar.getClientProperty(key) == null) {
progressBar.putClientProperty(key, progressBar.getString());
}
String fmt = (String)progressBar.getClientProperty(key);
if (progressBar.getValue() <= 0) {
progressBar.setString("");
}
else if (fmt == null) {
progressBar.setString(null);
}
else {
double pctComplete = progressBar.getValue() / 100.0;
long durSeconds = getTask().getExecutionDuration(TimeUnit.SECONDS);
long durMinutes = durSeconds / 60;
long remSeconds = (long)(0.5 + ((double)durSeconds / pctComplete)) - durSeconds;
long remMinutes = remSeconds / 60;
String s = String.format(fmt, durMinutes, durSeconds - (durMinutes * 60),
remMinutes, remSeconds - (remMinutes * 60));
progressBar.setString(s);
}
}
private void showBusyGlassPane(boolean f) {
RootPaneContainer rpc = null;
Component root = (Component)getTarget();
while(root != null) {
if (root instanceof RootPaneContainer) {
rpc = (RootPaneContainer)root;
break;
}
root = root.getParent();
}
if (rpc != null) {
if (f) {
JMenuBar menuBar = rpc.getRootPane().getJMenuBar();
if (menuBar != null) {
menuBar.putClientProperty(this, menuBar.isEnabled());
menuBar.setEnabled(false);
}
JComponent glassPane = new BusyGlassPane();
InputVerifier retainFocusWhileVisible = new InputVerifier() {
public boolean verify(JComponent c) {
return !c.isVisible();
}
};
glassPane.setInputVerifier(retainFocusWhileVisible);
Component oldGlassPane = rpc.getGlassPane();
rpc.getRootPane().putClientProperty(this, oldGlassPane);
rpc.setGlassPane(glassPane);
glassPane.setVisible(true);
glassPane.revalidate();
}
else {
JMenuBar menuBar = rpc.getRootPane().getJMenuBar();
if (menuBar != null) {
boolean enabled = (Boolean)menuBar.getClientProperty(this);
menuBar.putClientProperty(this, null);
menuBar.setEnabled(enabled);
}
Component oldGlassPane = (Component)rpc.getRootPane().getClientProperty(this);
rpc.getRootPane().putClientProperty(this, null);
if (!oldGlassPane.isVisible()) {
rpc.getGlassPane().setVisible(false);
}
rpc.setGlassPane(oldGlassPane); // sets oldGlassPane.visible
}
}
}
/* Note: unfortunately, the busy cursor is reset when the modal
* dialog is shown.
*/
private static class BusyGlassPane extends JPanel {
BusyGlassPane() {
super(null, false);
setVisible(false);
setOpaque(false);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
MouseInputListener blockMouseEvents = new MouseInputAdapter() {};
addMouseMotionListener(blockMouseEvents);
addMouseListener(blockMouseEvents);
}
}
/* If an action was specified then return the value of the
* actionName.BlockingDialogTimer.delay resource from the action's
* resourceMap. Otherwise return the value of the
* BlockingDialogTimer.delay resource from the Task's ResourceMap.
* The latter's default in defined in resources/Application.properties.
*/
private int blockingDialogDelay() {
Integer delay = null;
String key = "BlockingDialogTimer.delay";
ApplicationAction action = getAction();
if (action != null) {
ResourceMap actionResourceMap = action.getResourceMap();
String actionName = action.getName();
delay = actionResourceMap.getInteger(actionName + "." + key);
}
ResourceMap taskResourceMap = getTask().getResourceMap();
if ((delay == null) && (taskResourceMap != null)) {
delay = taskResourceMap.getInteger(key);
}
return (delay == null) ? 0 : delay.intValue();
}
private void showBlockingDialog(boolean f) {
if (f) {
if (modalDialog != null) {
String msg = String.format("unexpected InputBlocker state [%s] %s", f, this);
logger.warning(msg);
modalDialog.dispose();
}
modalDialog = createBlockingDialog();
ActionListener showModalDialog = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (modalDialog != null) { // already dismissed
modalDialog.setVisible(true);
}
}
};
Timer showModalDialogTimer = new Timer(blockingDialogDelay(), showModalDialog);
showModalDialogTimer.setRepeats(false);
showModalDialogTimer.start();
}
else {
if (modalDialog != null) {
modalDialog.dispose();
modalDialog = null;
}
else {
String msg = String.format("unexpected InputBlocker state [%s] %s", f, this);
logger.warning(msg);
}
}
}
@Override protected void block() {
switch (getScope()) {
case ACTION:
setActionTargetBlocked(true);
break;
case COMPONENT:
setComponentTargetBlocked(true);
break;
case WINDOW:
case APPLICATION:
showBusyGlassPane(true);
showBlockingDialog(true);
break;
}
}
@Override protected void unblock() {
switch (getScope()) {
case ACTION:
setActionTargetBlocked(false);
break;
case COMPONENT:
setComponentTargetBlocked(false);
break;
case WINDOW:
case APPLICATION:
showBusyGlassPane(false);
showBlockingDialog(false);
break;
}
}
}