/*******************************************************************************
* Copyright (c) 2007, 2010 Spring IDE Developers
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Spring IDE Developers - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.webflow.ui.graph.dialogs;
import java.util.ArrayList;
import java.util.List;
import ognl.Ognl;
import ognl.OgnlException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.internal.ui.dialogs.FilteredTypesSelectionDialog;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Text;
import org.springframework.ide.eclipse.core.java.JdtUtils;
import org.springframework.ide.eclipse.webflow.core.internal.model.WebflowModelXmlUtils;
import org.springframework.ide.eclipse.webflow.core.model.IActionElement;
import org.springframework.ide.eclipse.webflow.core.model.IAttributeEnabled;
import org.springframework.ide.eclipse.webflow.core.model.ICloneableModelElement;
import org.springframework.ide.eclipse.webflow.core.model.IStateTransition;
import org.springframework.ide.eclipse.webflow.core.model.IWebflowModelElement;
import org.springframework.ide.eclipse.webflow.ui.editor.outline.webflow.WebflowUIImages;
import org.springframework.ide.eclipse.webflow.ui.graph.WebflowUtils;
import org.springframework.util.StringUtils;
/**
* Properties {@link Dialog} implementation that enables the edition of {@link IStateTransition}elements.
* @author Christian Dupuis
* @since 2.0
*/
@SuppressWarnings("restriction")
public class StateTransitionPropertiesDialog extends TitleAreaDialog implements IDialogValidator {
private static final String EXPRESSION_PREFIX = "${";
private static final String EXPRESSION_SUFFIX = "}";
private List<IActionElement> actions;
private Button ognlButton;
private Button okButton;
private Text onText;
private Text onExceptionText;
private Button browseExceptionButton;
private SelectionListener buttonListener = new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
handleButtonPressed((Button) e.widget);
}
};
private IWebflowModelElement parent;
private IStateTransition transition;
private IStateTransition transitionClone;
private ActionComposite actionProperties;
private PropertiesComposite properties;
private boolean displayToBlock = false;
private Combo toStateText;
private Label historyLabel;
private Combo historyText;
private Button bindText;
private Label bindLabel;
@SuppressWarnings("unchecked")
public StateTransitionPropertiesDialog(Shell parentShell, IWebflowModelElement parent,
IStateTransition state) {
this(parentShell, parent, state, false);
}
@SuppressWarnings("unchecked")
public StateTransitionPropertiesDialog(Shell parentShell, IWebflowModelElement parent,
IStateTransition state, boolean displayToBlock) {
super(parentShell);
this.transition = state;
this.parent = parent;
this.transitionClone = ((ICloneableModelElement<IStateTransition>) this.transition)
.cloneModelElement();
actions = new ArrayList<IActionElement>();
if (this.transitionClone.getActions() != null) {
actions.addAll(this.transitionClone.getActions());
}
this.displayToBlock = displayToBlock;
}
protected void handleButtonPressed(Button widget) {
try {
IType throwable = JdtUtils.getJavaType(WebflowUtils.getActiveWebflowConfig()
.getProject().getProject(), "java.lang.Throwable");
IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(throwable
.newTypeHierarchy(new NullProgressMonitor()).getAllSubtypes(throwable));
FilteredTypesSelectionDialog dialog = new FilteredTypesSelectionDialog(getShell(), false,
new ProgressMonitorDialog(getShell()), searchScope, IJavaSearchConstants.CLASS);
dialog.setBlockOnOpen(true);
dialog.setTitle("Type Selection");
// dialog.setFilter("*");
if (Dialog.OK == dialog.open()) {
IType obj = (IType) dialog.getFirstResult();
this.onExceptionText.setText(obj.getFullyQualifiedName());
}
}
catch (JavaModelException e) {
}
this.validateInput();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
*/
@SuppressWarnings("unchecked")
protected void buttonPressed(int buttonId) {
if (buttonId == IDialogConstants.OK_ID) {
this.transitionClone.setOn(trimString(this.onText.getText()));
this.transitionClone.setOnException(trimString(this.onExceptionText.getText()));
if (!WebflowModelXmlUtils.isVersion1Flow(transition)) {
this.transitionClone.setBind(Boolean.toString(bindText.getSelection()));
this.transitionClone.setHistory(trimString(historyText.getText()));
}
if (this.displayToBlock) {
this.transitionClone.setToStateId(trimString(this.toStateText.getText()));
}
if (this.actions != null && this.actions.size() > 0) {
transitionClone.removeAll();
for (IActionElement a : this.actions) {
transitionClone.addAction(a);
}
}
else {
transitionClone.removeAll();
}
((ICloneableModelElement<IStateTransition>) this.transition)
.applyCloneValues(this.transitionClone);
}
super.buttonPressed(buttonId);
}
protected void configureShell(Shell shell) {
super.configureShell(shell);
shell.setText(getShellTitle());
shell.setImage(getImage());
}
protected void createButtonsForButtonBar(Composite parent) {
// create OK and Cancel buttons by default
okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
// do this here because setting the text will set enablement on the
// ok button
onText.setFocus();
if (this.transition != null
&& (this.transition.getOn() != null || this.transition.getOnException() != null)) {
okButton.setEnabled(true);
}
else {
okButton.setEnabled(false);
}
}
protected Control createContents(Composite parent) {
Control contents = super.createContents(parent);
setTitle(getTitle());
setMessage(getMessage());
return contents;
}
protected Control createDialogArea(Composite parent) {
Composite parentComposite = (Composite) super.createDialogArea(parent);
Composite composite = new Composite(parentComposite, SWT.NULL);
GridLayout layout = new GridLayout();
layout.numColumns = 1;
composite.setLayout(layout);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
TabFolder folder = new TabFolder(composite, SWT.NULL);
folder.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
TabItem item1 = new TabItem(folder, SWT.NULL);
item1.setText("General");
item1.setImage(WebflowUIImages.getImage(WebflowUIImages.IMG_OBJS_TRANSITION));
TabItem item2 = new TabItem(folder, SWT.NULL);
TabItem item4 = new TabItem(folder, SWT.NULL);
Composite nameGroup = new Composite(folder, SWT.NULL);
nameGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
GridLayout layout1 = new GridLayout();
layout1.numColumns = 2;
layout1.marginWidth = 0;
layout1.marginHeight = 0;
nameGroup.setLayout(layout1);
Group groupActionType = new Group(nameGroup, SWT.NULL);
groupActionType.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout layoutAttMap = new GridLayout();
layoutAttMap.numColumns = 3;
layoutAttMap.marginWidth = 5;
groupActionType.setLayout(layoutAttMap);
groupActionType.setText(" Transition ");
Label onLabel = new Label(groupActionType, SWT.NONE);
onLabel.setText("On");
onText = new Text(groupActionType, SWT.SINGLE | SWT.BORDER);
if (this.transition != null && this.transition.getOn() != null) {
this.onText.setText(this.transition.getOn());
}
onText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
onText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
validateInput();
}
});
new Label(groupActionType, SWT.NONE);
new Label(groupActionType, SWT.NONE);
ognlButton = new Button(groupActionType, SWT.CHECK);
ognlButton.setText("Parse OGNL transition criteria");
ognlButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
validateInput();
}
});
new Label(groupActionType, SWT.NONE);
Label onExceptionLabel = new Label(groupActionType, SWT.NONE);
onExceptionLabel.setText("On Exception");
onExceptionText = new Text(groupActionType, SWT.SINGLE | SWT.BORDER);
if (this.transition != null && this.transition.getOnException() != null) {
this.onExceptionText.setText(this.transition.getOnException());
}
onExceptionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
onExceptionText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
validateInput();
}
});
browseExceptionButton = new Button(groupActionType, SWT.PUSH);
browseExceptionButton.setText("...");
browseExceptionButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
browseExceptionButton.addSelectionListener(buttonListener);
if (this.displayToBlock) {
Label toStateLabel = new Label(groupActionType, SWT.NONE);
toStateLabel.setText("To State");
toStateText = new Combo(groupActionType, SWT.DROP_DOWN | SWT.READ_ONLY);
toStateText.setItems(WebflowUtils.getStateId(this.parent));
if (this.transition != null && this.transition.getToStateId() != null) {
this.toStateText.setText(this.transition.getToStateId());
}
toStateText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
toStateText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
validateInput();
}
});
new Label(groupActionType, SWT.NONE);
}
if (!WebflowModelXmlUtils.isVersion1Flow(transition)) {
bindLabel = new Label(groupActionType, SWT.NONE);
bindLabel.setText("Bind");
bindText = new Button(groupActionType, SWT.CHECK | SWT.BORDER);
if (this.transition != null && this.transition.getBind() != null
&& this.transition.getBind().equalsIgnoreCase("true")) {
this.bindText.setSelection(true);
}
bindText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
new Label(groupActionType, SWT.NONE);
historyLabel = new Label(groupActionType, SWT.NONE);
historyLabel.setText("History");
historyText = new Combo(groupActionType, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
historyText.setItems(new String[] { "", "preserve", "discard", "invalidate" });
if (this.transition != null && this.transition.getHistory() != null) {
this.historyText.setText(this.transition.getHistory());
}
historyText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
new Label(groupActionType, SWT.NONE);
}
item1.setControl(nameGroup);
actionProperties = new ActionComposite(this, item2, getShell(), this.actions,
this.transitionClone, IActionElement.ACTION_TYPE.ACTION);
item2.setControl(actionProperties.createDialogArea(folder));
properties = new PropertiesComposite(this, item4, getShell(),
(IAttributeEnabled) this.transitionClone);
item4.setControl(properties.createDialogArea(folder));
applyDialogFont(parentComposite);
return parentComposite;
}
private String cutExpression(String encodedCriteria) {
return encodedCriteria.substring(EXPRESSION_PREFIX.length(), encodedCriteria.length()
- EXPRESSION_SUFFIX.length());
}
public String getMessage() {
return "Enter the details for the state transition";
}
public IWebflowModelElement getModelElementParent() {
return this.parent;
}
protected String getShellTitle() {
return "Transition";
}
protected String getTitle() {
return "Transition properties";
}
protected Image getImage() {
return WebflowUIImages.getImage(WebflowUIImages.IMG_OBJS_TRANSITION);
}
protected void handleTableSelectionChanged() {
}
protected void showError(String error) {
super.setErrorMessage(error);
}
public String trimString(String string) {
if (string != null && string == "") {
string = null;
}
return string;
}
public void validateInput() {
String id = this.onText.getText();
String onexception = this.onExceptionText.getText();
boolean error = false;
StringBuffer errorMessage = new StringBuffer();
if (!StringUtils.hasText(id) && !StringUtils.hasText(onexception)) {
errorMessage.append("A valid on or on-exception attribute is required. ");
error = true;
}
if (this.ognlButton.getSelection()) {
if (!id.startsWith(EXPRESSION_PREFIX) || !id.endsWith(EXPRESSION_SUFFIX)) {
errorMessage
.append("A valid OGNL expression needs to start with '${' and ends with '}'. ");
error = true;
}
else {
try {
Ognl.parseExpression(this.cutExpression(id));
}
catch (OgnlException e) {
errorMessage.append("Malformed OGNL expression. ");
error = true;
}
}
}
if (error) {
getButton(OK).setEnabled(false);
setErrorMessage(errorMessage.toString());
}
else {
getButton(OK).setEnabled(true);
setErrorMessage(null);
}
}
}