/* * Copyright (C) 2006-2009 Sun Microsystems, Inc. All rights reserved. * Copyright (C) 2010 Peransin Nicolas. All rights reserved. * Use is subject to license terms. */ package org.mypsycho.swing.app.task; import java.awt.Component; import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.logging.Level; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JMenuBar; import javax.swing.JPanel; import javax.swing.RootPaneContainer; import javax.swing.Timer; import javax.swing.event.MouseInputAdapter; import javax.swing.event.MouseInputListener; import org.mypsycho.beans.Inject; import org.mypsycho.beans.Injectable; import org.mypsycho.beans.InjectionContext; import org.mypsycho.beans.InjectionStack; import org.mypsycho.swing.app.Application; import org.mypsycho.swing.app.utils.SwingHelper; @Inject(deferred=DefaultInputBlocker.DIALOG_PROP) public class DefaultInputBlocker extends Task.InputBlocker implements Injectable { public static final String DIALOG_PROP = "dialog"; private JDialog modalDialog = null; protected long displayDelay = 250; // in ms InjectionStack context = new InjectionStack(this); public DefaultInputBlocker(Task<?, ?> task, Task.BlockingScope scope, Object target, Application app) { super(task, scope, target, app); } 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 } /* * (non-Javadoc) * * @see com.psycho.beans.Injectable#initContext(com.psycho.beans.InjectionContext) */ @Override public void initResources(InjectionContext context) { this.context.addContext(context); } public long getDisplayDelay() { return displayDelay; } public void setDisplayDelay(long dialogDelay) { this.displayDelay = dialogDelay; } /* 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() { /* BlockingDialog.title = Busy BlockingDialog.cancelButton.text = &Cancel BlockingDialog.progressBar.stringPainted = true BlockingDialog.progressBar.string = %02d:%02d, %02d:%02d remaining BlockingDialogTimer.delay = 250 */ InputBlockerPane optionPane = new InputBlockerPane(getTask()); // Structural injection getApplicationContext().getResourceManager().inject(optionPane, getTask().getLocale()); // Contextual injection context.inject("dialog", optionPane); Component dialogOwner = (Component) getTarget(); JDialog dialog = optionPane.createDialog(dialogOwner); dialog.pack(); return dialog; } private void showBusyGlassPane(boolean f) { /* * Use SwingHelper.findRootPaneContainer to find the nearest * RootPaneContainer ancestor. * FIXED: BSAF-77 */ RootPaneContainer rpc = SwingHelper.findRootPaneContainer((Component) getTarget()); if (rpc == null) { return; } 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() { @Override 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. */ @SuppressWarnings("serial") 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); } } private void showBlockingDialog(boolean visible) { boolean old = (modalDialog != null); if (old == visible) { String msg = "Unexpected InputBlocker state [" + visible + "] " + this; getApplication().exceptionThrown(Level.INFO, this, msg, null); } if (old) { modalDialog.dispose(); modalDialog = null; } if (visible) { modalDialog = createBlockingDialog(); int delay = (int) getDisplayDelay(); if (delay <= 0) { modalDialog.setVisible(true); } else { ActionListener showModalDialog = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (modalDialog != null) { // already dismissed modalDialog.setVisible(true); } } }; Timer showModalDialogTimer = new Timer(delay, showModalDialog); showModalDialogTimer.setRepeats(false); showModalDialogTimer.start(); } } } @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; } } }