/* AbstractDialog.java created 2007-09-11
*
*/
package org.signalml.app.view.common.dialogs;
import static org.signalml.app.util.i18n.SvarogI18n._;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URL;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.border.EmptyBorder;
import org.apache.log4j.Logger;
import org.signalml.app.model.components.validation.ValidationErrors;
import org.signalml.app.util.IconUtils;
import org.signalml.app.view.common.dialogs.errors.Dialogs;
import org.signalml.app.view.common.dialogs.errors.ValidationErrorsDialog;
import org.signalml.plugin.export.SignalMLException;
import org.signalml.plugin.impl.PluginAccessClass;
import org.signalml.util.SvarogConstants;
import org.springframework.validation.Errors;
/**
* The abstract dialog, from which every dialog in Svarog should inherit.
* Contains the control pane (with OK and CANCEL button).
* Performs the operations necessary to create and close (either with OK or
* CANCEL) this dialog, so that the children of this class must only implement:
* <ul>
* <li>{@link #fillDialogFromModel(Object)}</li>
* <li>{@link #fillModelFromDialog(Object)}</li>
* <li>{@link #validateDialog(Object, Errors)}</li>
* <li>{@link #supportsModelClass(Class)}</li>
* <li>{@link #createInterface()}</li>
* </ul>
*
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public abstract class AbstractDialog extends JDialog {
static final long serialVersionUID = 1L;
protected transient final Logger logger = Logger.getLogger(getClass());
/**
* true if the initialization has been performed, false otherwise
*/
private boolean initialized = false;
/**
* true if there is a parent window for this dialog, false otherwise
*/
private boolean hasParent = false;
/**
* the model with data from which this dialog is filled and in which
* the results of this dialog are stored
*/
protected Object currentModel;
/**
* true if this dialog was closed with the OK button
* (or in other way that means the success) and there were no errors
* during validation and filling the model,
* false otherwise.
*/
boolean closedWithOk = false;
/**
* the action that is performed when the dialog is closed with OK button
*/
OkAction okAction;
/**
* the action that is performed when the dialog is closed with CANCEL button
*/
private CancelAction cancelAction;
/**
* the button for the {@link OkAction}
*/
private JButton okButton;
/**
* the button for the {@link CancelAction}
*/
private JButton cancelButton;
/**
* the container that holds the components parented by the root pane
*/
private JPanel contentPane;
/**
* the pane with the {@link #okButton OK} and {@link #cancelButton CANCEL}
* button
*/
private JPanel controlPane;
/**
* the actual interface of this dialog that is located in the
* {@link #contentPane content pane}
*/
private JComponent interfaceComponent;
/**
* the dialog that shows errors gathered during the
* {@link #validateDialog(Object, Errors) validation} of the dialog
*/
private ValidationErrorsDialog errorsDialog;
/**
* the dialog in which the help for this dialog is displayed
*/
private HelpDialog helpDialog;
/**
* Constructor. Sets the default source of messages (obtained from
* {@link PluginAccessClass}).
*/
public AbstractDialog() {
super();
setModal(true);
setLocationRelativeTo(null);
}
/**
* Constructor. Sets parent window and if this dialog
* blocks top-level windows.
* @param w the parent window or null if there is no parent
* @param isModal true, dialog blocks top-level windows, false otherwise
*/
public AbstractDialog(Window w, boolean isModal) {
super(w, (isModal) ? Dialog.ModalityType.APPLICATION_MODAL : Dialog.ModalityType.MODELESS);
if (w != null) {
hasParent = true;
}
}
/**
* If dialog is not initialized yet the initialization is called.
*/
public final void initializeNow() {
if (!initialized) {
initialize();
initialized = true;
}
}
/**
* Initializes this dialog.
* Sets the action which is called when window is being closed.
* Initializes the {@link #initializeControlPane() control} and
* {@link #initializeContentPane() content} pane.
* If the dialog should be canceled on escape sets actions necessary to
* do it.
*/
protected void initialize() {
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
setMaximumSize(SvarogConstants.MIN_ASSUMED_DESKTOP_SIZE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent arg0) {
if (isCancellable()) {
getCancelAction().actionPerformed(new ActionEvent(this,0,"cancel"));
} else {
getOkAction().actionPerformed(new ActionEvent(this,0,"cancel"));
}
}
});
contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
if (isControlPanelEquipped()) {
initializeControlPane();
}
initializeContentPane();
getRootPane().setContentPane(contentPane);
if (isCancelOnEscape()) {
KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE");
getRootPane().getActionMap().put("ESCAPE", getCancelAction());
}
pack();
initialized = true;
}
/**
* Adds a {@link #getControlPane() control pane} to the content pane
* and adds the {@link #addContextHelp() context help}.
*/
protected final void initializeControlPane() {
JPanel controlPane = getControlPane();
contentPane.add(controlPane,BorderLayout.SOUTH);
addContextHelp();
}
/**
* If the {@link #getContextHelpURL() context help URL} is not null the
* {@link ContextHelpAction action} that shows help is created, it is added
* as a button to the control pane and the key {@code F1} is associated with it.
*/
protected void addContextHelp() {
JPanel controlPane = getControlPane();
URL contextHelpURL = getContextHelpURL();
if (contextHelpURL != null) {
controlPane.add(Box.createHorizontalStrut(5), 0);
ContextHelpAction helpAction = createContextHelpAction(contextHelpURL);
KeyStroke f1 = KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0, false);
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(f1, "HELP");
getRootPane().getActionMap().put("HELP", helpAction);
controlPane.add(new JButton(helpAction), 0);
}
}
/**
* Creates the action that shows help with the given URL to help.
* @param helpURL the URL to help
* @return created action
*/
public ContextHelpAction createContextHelpAction(URL helpURL) {
return new ContextHelpAction(helpURL);
}
/**
* Creates the control pane.
* Adds OK button and, if dialog can be canceled, the CANCEL button.
* @return the created pane
*/
protected JPanel createControlPane() {
JPanel controlPane = new JPanel();
controlPane.setLayout(new BoxLayout(controlPane, BoxLayout.X_AXIS));
controlPane.setBorder(new EmptyBorder(3,0,0,0));
controlPane.add(Box.createHorizontalGlue());
getRootPane().setDefaultButton(getOkButton());
controlPane.add(getOkButton());
if (isCancellable()) {
controlPane.add(Box.createHorizontalStrut(3));
controlPane.add(getCancelButton());
}
return controlPane;
}
/**
* Returns the control pane.
* If it doesn't exist, {@link #createControlPane() creates is}.
* @return the control pane
*/
protected final JPanel getControlPane() {
if (controlPane == null) {
controlPane = createControlPane();
}
return controlPane;
}
/**
* Initializes the content pane.
* Adds the {@link #getInterface() interface} to it.
*/
protected void initializeContentPane() {
contentPane.setBorder(new EmptyBorder(3,3,3,3));
contentPane.add(getInterface(),BorderLayout.CENTER);
}
/**
* Returns if this dialog is initialized.
* @return true if this dialog is initialized, false otherwise
*/
public final boolean isInitialized() {
return initialized;
}
/**
* Returns if this dialog can be canceled.
* @return true if this dialog can be canceled, false otherwise
*/
public boolean isCancellable() {
return true;
}
/**
* Returns if this dialog can be canceled with escape button.
* @return true if this dialog can be canceled with escape button,
* false otherwise
*/
public boolean isCancelOnEscape() {
return true;
}
/**
* Returns if the control pane should be used.
* @return true if the control pane should be used,
* false otherwise
*/
public boolean isControlPanelEquipped() {
return true;
}
/**
* Creates the interface of this dialog.
* Contents of this interface depends on the implementation.
* @return the interface of this dialog
*/
protected abstract JComponent createInterface();
/**
* Returns the interface of this dialog.
* If it doesn't exist it is created
* @return the interface of this dialog
*/
public JComponent getInterface() {
if (interfaceComponent == null) {
interfaceComponent = createInterface();
}
return interfaceComponent;
}
/**
* Returns if the model can be of the given type.
* @param clazz the type of the model
* @return true the model can be of the given type, false otherwise
*/
public abstract boolean supportsModelClass(Class<?> clazz);
/**
* Fills the fields of this dialog from the given model.
* @param model the model from which this dialog will be filled.
* @throws SignalMLException TODO when it is thrown
*/
protected abstract void fillDialogFromModel(Object model) throws SignalMLException;
/**
* Fills the model with the data from this dialog (user input).
* @param model the model to be filled
* @throws SignalMLException TODO when it is thrown
*/
public abstract void fillModelFromDialog(Object model) throws SignalMLException;
/**
* Shows this dialog.
* If it is not initialized, the initialization is done.
* Calls {@link #showDialog(Object)}.
* @param model the model from which this dialog will be filled and
* to which the results will be written
* @param centered boolean the location of this dialog should be set
* relative to the parent component
* @return if the dialog was closed with OK
*/
public final boolean showDialog(Object model, boolean centered) {
if (!initialized) {
initialize();
initialized = true;
}
if (centered) {
setLocationRelativeTo(getParent());
}
return showDialog(model);
}
/**
* Shows this dialog.
* If it is not initialized, the initialization is done.
* Calls {@link #showDialog(Object)}.
* The location of this dialog is set using
* {@link #centerInParent(double, double)}.
* @param model the model from which this dialog will be filled and
* to which the results will be written
* @param xpos the x proportion of the parent window at which this dialog
* should be located
* @param ypos the y proportion of the parent window at which this dialog
* should be located
* @return true if the dialog was closed with OK, false otherwise
*/
public final boolean showDialog(Object model, double xpos, double ypos) {
if (!initialized) {
initialize();
initialized = true;
}
centerInParent(xpos, ypos);
return showDialog(model);
}
/**
* Initializes this dialog, fills it from model and makes it visible.
* @param model the model from which this dialog will be filled and
* to which the results will be written
* @return true if the dialog was closed with OK, false otherwise
* @throws ClassCastException if the model is not of the type supported
* by this dialog
*/
public boolean showDialog(Object model) {
if (!initialized) {
initialize();
initialized = true;
}
resetDialog();
if (model != null) {
if (!supportsModelClass(model.getClass())) {
throw new ClassCastException("Model class [" + model.getClass().toString() + "] not supported by dialog [" + this.getClass().toString() + "]");
}
} else {
if (!supportsModelClass(null)) {
throw new ClassCastException("Model required for dialog [" + this.getClass().toString() + "]");
}
}
try {
fillDialogFromModel(model);
} catch (SignalMLException ex) {
logger.error("Exception when filling the dialog from the model", ex);
Dialogs.showExceptionDialog(this, ex);
return false;
}
currentModel = model;
closedWithOk = false;
setVisible(true);
// if the dialog was modal then closedWithOk may have been changed
// otherwise, the method always returns false
return closedWithOk;
}
/**
* Resets the fields of the dialog.
*/
protected void resetDialog() {
// do nothing
}
/**
* Called when the dialog was canceled.
* @return true if the operation was successful, false otherwise
*/
protected boolean onCancel() {
// do nothing
return true;
}
/**
* Called when the dialog is closing.
*/
protected void onDialogClose() {
// do nothing
}
/**
* Called when the dialog is closing with OK.
*/
protected void onDialogCloseWithOK() {
// do nothing
}
public boolean validateDialog() {
if (currentModel != null) {
//Errors errors = new BindException(currentModel, "data");
ValidationErrors errors = new ValidationErrors();
try {
validateDialog(currentModel, errors);
} catch (SignalMLException ex) {
logger.error("Dialog validation threw an exception", ex);
Dialogs.showExceptionDialog(AbstractDialog.this, ex);
currentModel = null;
closedWithOk = false;
setVisible(false);
return false;
}
if (errors.hasErrors()) {
showValidationErrors(errors);
return false;
}
}
return true;
}
/**
* Checks if this dialog is properly filled.
* @param model the model for this dialog
* @param errors the object in which errors are stored
* @throws SignalMLException TODO when it is thrown
*/
public void validateDialog(Object model, ValidationErrors errors) throws SignalMLException {
/* do nothing */
}
/**
* Returns the URL to context help.
* @return the URL to context help
*/
protected URL getContextHelpURL() {
return null;
}
/**
* Shows the validation errors dialog with given validation errors.
* @param errors the errors to be displayed
*/
protected void showValidationErrors(ValidationErrors errors) {
getErrorsDialog().showDialog(errors, true);
}
/**
* Sets the location of this dialog to be in the centre of parent window.
*/
public void centerInParent() {
centerInParent(0.5, 0.5);
}
/**
* Sets the location of this dialog so that it is located
* {@code xpos*parentWidth} from the left border of the parent window and
* {@code ypos*parentHeight} from the top border of the parent window.
* If there is no parent window, the whole screen is considered.
* @param xpos the x proportion of the parent window at which this dialog
* should be located
* @param ypos the y proportion of the parent window at which this dialog
* should be located
*/
public void centerInParent(double xpos, double ypos) {
final double safeXpos = xpos > 1.0 ? 1.0 : (xpos < 0.0 ? 0.0 : xpos);
final double safeYpos = ypos > 1.0 ? 1.0 : (ypos < 0.0 ? 0.0 : ypos);
Dimension d = null;
if (hasParent) {
d = getParent().getSize();
} else {
// this dialog is alone on the screen
Toolkit tk = Toolkit.getDefaultToolkit();
d = tk.getScreenSize();
}
int x = (int)((d.width - getWidth()) * safeXpos);
int y = (int)((d.height - getHeight()) * safeYpos);
if (isUndecorated() && hasParent) {
Point parentLoc = getParent().getLocationOnScreen();
x += parentLoc.x;
y += parentLoc.y;
}
setLocation(x, y);
}
/**
* Sets the location of this dialog so that it is located
* {@code xpos*parentWidth} from the left border of the given component and
* {@code ypos*parentHeight} from the top border of the given component.
* If there is no parent window, the whole screen is considered.
* @param top the component in which this dialog is to centered
* @param xpos the x proportion of the given component at which this dialog
* should be located
* @param ypos the y proportion of the given component at which this dialog
* should be located
*/
public void centerInComponent(Component top, double xpos, double ypos) {
final double safeXpos = xpos > 1.0 ? 1.0 : (xpos < 0.0 ? 0.0 : xpos);
final double safeYpos = ypos > 1.0 ? 1.0 : (ypos < 0.0 ? 0.0 : ypos);
Dimension d;
if (top != null) {
d = top.getSize();
} else {
Toolkit tk = Toolkit.getDefaultToolkit();
d = tk.getScreenSize();
}
int x = (int)((d.width - getWidth()) * safeXpos);
int y = (int)((d.height - getHeight()) * safeYpos);
Point parentLoc;
if (top != null) {
parentLoc = top.getLocationOnScreen();
} else {
parentLoc = new Point(0,0);
}
x += parentLoc.x;
y += parentLoc.y;
setLocation(x, y);
}
/**
* Returns the validation errors dialog.
* If it doesn't exist it is created.
* @return the errors dialog
*/
protected synchronized ValidationErrorsDialog getErrorsDialog() {
if (errorsDialog == null) {
errorsDialog = new ValidationErrorsDialog(this, true);
}
return errorsDialog;
}
/**
* Returns the {@link HelpDialog help dialog}.
* If it doesn't exist it is created.
* @return the help dialog
*/
protected synchronized HelpDialog getHelpDialog() {
if (helpDialog == null) {
helpDialog = new HelpDialog(this, true);
}
return helpDialog;
}
/**
* Returns the {@link OkAction}.
* If it doesn't exist it is created.
* @return the OK action
*/
protected synchronized OkAction getOkAction() {
if (okAction == null) {
okAction = new OkAction();
}
return okAction;
}
/**
* Returns the {@link CancelAction}.
* If it doesn't exist it is created.
* @return the CANCEL action
*/
protected synchronized CancelAction getCancelAction() {
if (cancelAction == null) {
cancelAction = new CancelAction();
}
return cancelAction;
}
/**
* Returns the button for the {@link OkAction}.
* If it doesn't exist it is created.
* @return the button for OK action
*/
protected synchronized JButton getOkButton() {
if (okButton == null) {
okButton = new JButton(getOkAction());
}
return okButton;
}
/**
* Returns the button for the {@link CancelAction}.
* If it doesn't exist it is created.
* @return the button for CANCEL action
*/
protected synchronized JButton getCancelButton() {
if (cancelButton == null) {
cancelButton = new JButton(getCancelAction());
}
return cancelButton;
}
/**
* Returns the model for this dialog.
* @return the model for this dialog
*/
protected Object getCurrentModel() {
return currentModel;
}
/**
* Sets the model for this dialog.
* @param currentModel the model for this dialog
*/
protected void setCurrentModel(Object currentModel) {
this.currentModel = currentModel;
}
/**
* Returns true if this dialog was closed with the OK button
* (or in other way that means the success) and there were no errors
* during validation and filling the model,
* false otherwise.
* @return true if this dialog was closed with the OK button
* (or in other way that means the success) and there were no errors
* during validation and filling the model,
* false otherwise.
*/
public boolean isClosedWithOk() {
return closedWithOk;
}
/**
* Sets if this dialog was closed with the OK button
* (or in other way that means the success) and there were no errors
* during validation and filling the model.
* @param closedWithOk the value to set
*/
protected void setClosedWithOk(boolean closedWithOk) {
this.closedWithOk = closedWithOk;
}
protected void onOkPressed() {
if (validateDialog() == false)
return;
try {
fillModelFromDialog(currentModel);
} catch (SignalMLException ex) {
logger.error("Exception when filling the model from the dialog", ex);
Dialogs.showExceptionDialog(AbstractDialog.this, ex);
currentModel = null;
closedWithOk = false;
setVisible(false);
return;
}
currentModel = null;
closedWithOk = true;
setVisible(false);
onDialogCloseWithOK();
onDialogClose();
}
/**
* The OK action.
* Contains the icon which is used to create the button.
* When this action is performed the dialog is
* {@link AbstractDialog#validateDialog(Object, Errors) validated},
* the model is {@link AbstractDialog#fillModelFromDialog(Object) filled}
* from the dialog and the dialog is set to be invisible.
*/
protected class OkAction extends AbstractAction {
private static final long serialVersionUID = 1L;
/**
* Constructor. Sets the description and the icon.
*/
public OkAction() {
super(_("Ok"));
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/ok.png"));
}
/**
* Called when this action is performed.
* {@link AbstractDialog#validateDialog(Object, Errors) Validates}
* the dialog, {@link AbstractDialog#fillModelFromDialog(Object) fills}
* the model from it and sets the dialog to be invisible.
*/
@Override
public void actionPerformed(ActionEvent ev) {
onOkPressed();
}
}
/**
* The CANCEL action.
* Contains the icon which is used to create the button.
* When this action is performed {@link AbstractDialog#closedWithOk}
* is set to be false and the dialog is set to be invisible.
*/
protected class CancelAction extends AbstractAction {
private static final long serialVersionUID = 1L;
/**
* Constructor. Sets the description and the icon.
*/
public CancelAction() {
super(_("Cancel"));
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/cancel.png"));
}
/**
* Called when this action is performed.
* Sets {@link AbstractDialog#closedWithOk} to false,
* sets the dialog to be invisible and performs
* {@link AbstractDialog#onDialogClose() closing operations}.
*/
@Override
public void actionPerformed(ActionEvent ev) {
boolean ok = onCancel();
if (!ok) {
return;
}
currentModel = null;
closedWithOk = false;
setVisible(false);
onDialogClose();
}
}
/**
* The action that displays the context help.
* When this action is performed {@link HelpDialog help dialog}
* is created.
*/
protected class ContextHelpAction extends AbstractAction {
private static final long serialVersionUID = 1L;
/**
* the URL to help
*/
private URL contextHelpURL;
/**
* Constructor. Sets the description, icon, tooltip and the URL to help.
* @param url the address where the help is stored
*/
public ContextHelpAction(URL url) {
super();
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/help.png"));
putValue(AbstractAction.SHORT_DESCRIPTION,_("Display context help for this dialog"));
contextHelpURL = url;
}
/**
* Creates the {@link HelpDialog help dialog} with the given URL to
* the help.
*/
@Override
public void actionPerformed(ActionEvent ev) {
HelpDialog helpDialog = getHelpDialog();
helpDialog.reset();
helpDialog.showDialog(contextHelpURL, true);
}
}
}