/*
* Copyright 2010-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.eclipse.elasticbeanstalk.server.ui.configEditor;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateSetStrategy;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.ISetChangeListener;
import org.eclipse.core.databinding.observable.set.SetChangeEvent;
import org.eclipse.core.databinding.observable.set.WritableSet;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
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.FontMetrics;
import org.eclipse.swt.graphics.GC;
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.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.wst.server.ui.editor.ServerEditorSection;
import com.amazonaws.eclipse.databinding.ChainValidator;
import com.amazonaws.eclipse.databinding.DecorationChangeListener;
import com.amazonaws.eclipse.elasticbeanstalk.Environment;
import com.amazonaws.eclipse.elasticbeanstalk.server.ui.databinding.ConfigurationSettingValidator;
import com.amazonaws.services.elasticbeanstalk.model.ConfigurationOptionDescription;
/**
* Abstract base editor section that knows how to create controls for an editable option.
*/
public class EnvironmentConfigEditorSection extends ServerEditorSection {
/** The section widget we're managing */
protected Section section;
protected AbstractEnvironmentConfigEditorPart parentEditor;
protected EnvironmentConfigDataModel model;
protected DataBindingContext bindingContext;
protected final Environment environment;
protected FormToolkit toolkit;
protected String namespace;
protected List<ConfigurationOptionDescription> options;
/**
* Sets the list of options that this section will present to the user, one
* control per option. Can be set any time before the page is constructed.
*/
public void setOptions(List<ConfigurationOptionDescription> options) {
this.options = options;
}
/**
* Constructs a new section for one namespace.
*
* @param parentEditor
* The editorPart that created this section
* @param namespace
* The namespace of this section
* @param options
* The options in the namespace
*/
public EnvironmentConfigEditorSection(AbstractEnvironmentConfigEditorPart parentEditor,
EnvironmentConfigDataModel model, Environment environment, DataBindingContext bindingContext,
String namespace, List<ConfigurationOptionDescription> options) {
this.parentEditor = parentEditor;
this.bindingContext = bindingContext;
this.environment = environment;
this.model = model;
this.namespace = namespace;
this.options = options;
}
public int getNumControls() {
return options.size();
}
@Override
public void createSection(Composite parent) {
super.createSection(parent);
toolkit = getFormToolkit(parent.getDisplay());
section = getSection(parent);
GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, false);
section.setLayoutData(layoutData);
Composite composite = toolkit.createComposite(section);
GridLayout layout = new GridLayout(2, false);
layout.marginHeight = 5;
layout.marginWidth = 10;
layout.verticalSpacing = 10;
layout.horizontalSpacing = 15;
composite.setLayout(layout);
composite.setLayoutData(layoutData);
toolkit.paintBordersFor(composite);
section.setClient(composite);
section.setLayout(layout);
section.setLayoutData(layoutData);
createSectionControls(composite);
section.setDescription(getSectionDescription());
section.setText(getSectionName());
}
/**
* Creates a section in the given composite.
*/
protected Section getSection(Composite parent) {
return toolkit.createSection(parent, ExpandableComposite.TWISTIE | ExpandableComposite.EXPANDED
| ExpandableComposite.TITLE_BAR | ExpandableComposite.FOCUS_TITLE);
}
/**
* Creates all controls for the page using the composite given.
*/
protected void createSectionControls(Composite composite) {
for ( ConfigurationOptionDescription o : options ) {
createOptionControl(composite, o);
}
}
/**
* Returns the name of this editor section.
*/
protected String getSectionName() {
return namespace;
}
/**
* Returns the description for this editor section.
*/
protected String getSectionDescription() {
return null;
}
/**
* Creates the appropriate control to display and change the option given.
*/
protected void createOptionControl(Composite parent, ConfigurationOptionDescription option) {
String valueType = option.getValueType();
if ( valueType.equals("Scalar") ) {
if (option.getValueOptions().isEmpty()) {
createTextField(parent, option);
} else {
createCombo(parent, option);
}
} else if ( valueType.equals("Boolean") ) {
createCheckbox(parent, option);
} else if ( valueType.equals("List") ) {
if (option.getValueOptions().isEmpty()) {
createTextField(parent, option);
} else {
createList(parent, option);
}
} else if ( valueType.equals("CommaSeparatedList")) {
createCommaSeparatedList(parent, option);
} else if (valueType.equals("KeyValueList")) {
createKeyValueList(parent, option);
} else {
Label label = createLabel(toolkit, parent, option);
label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
Label label1 = toolkit.createLabel(parent, (option.getValueOptions().toString() + "(" + valueType + ")"));
label1.setForeground(toolkit.getColors().getColor(IFormColors.TITLE));
}
}
/**
* Creates a key value list control with the option given
*/
private void createKeyValueList(Composite parent, ConfigurationOptionDescription option) {
createTextField(parent, option);
}
/**
* Creates a comma separated list with the option given
*/
private void createCommaSeparatedList(Composite parent, ConfigurationOptionDescription option) {
createTextField(parent, option);
}
/**
* Creates a list of checkable options with the option given.
*/
protected void createList(Composite parent, ConfigurationOptionDescription option) {
GridData labelData = new GridData(SWT.LEFT, SWT.TOP, false, false);
labelData.horizontalSpan = 2;
Label label = createLabel(toolkit, parent, option);
label.setLayoutData(labelData);
/*
* This process is complicated and differs from the rest of the
* mutliple-view data model binding in that it doesn't use the data
* model singleton as a proxy to generate an observable. This is because
* the observable set of values is created when the model is.
*
* It also requires explicit two-way wiring via listeners: one chunk to
* update the model when the controls change, and another to update the
* controls when the model changes. One-way listening is sufficient to
* update the model, but not to make the two views of the model align.
*/
final IObservableSet modelValues = (IObservableSet) model.getEntry(option);
final IObservableSet controlValues = new WritableSet();
controlValues.addAll(modelValues);
final List<Button> checkboxButtons = new ArrayList<Button>();
int i = 0;
Button lastButton = null;
/*
* Each button needs a listener to update the observed set of model
* values.
*/
for ( final String valueOption : option.getValueOptions() ) {
final Button button = toolkit.createButton(parent, valueOption, SWT.CHECK);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (button.getSelection()) {
controlValues.add(valueOption);
} else {
controlValues.remove(valueOption);
}
}
});
button.addSelectionListener(new DirtyMarker());
checkboxButtons.add(button);
lastButton = button;
i++;
}
/*
* Make sure we don't have an odd number of elements screwing up the
* rest of the layout.
*/
if ( i % 2 != 0 ) {
GridData buttonData = new GridData(SWT.LEFT, SWT.TOP, false, false);
buttonData.horizontalSpan = 2;
lastButton.setLayoutData(labelData);
}
Binding bindSet = bindingContext.bindSet(controlValues, modelValues, new UpdateSetStrategy(UpdateSetStrategy.POLICY_UPDATE),
new UpdateSetStrategy(UpdateSetStrategy.POLICY_UPDATE));
/*
* The observed set of model values needs a listener to update the
* controls, in case the selection event came from another set of
* controls with which we need to synchronize.
*/
controlValues.addSetChangeListener(new ISetChangeListener() {
public void handleSetChange(SetChangeEvent event) {
for ( Button button : checkboxButtons ) {
boolean checked = false;
for ( Object value : modelValues ) {
if (button.getText().equals(value)) {
checked = true;
break;
}
}
button.setSelection(checked);
}
}
});
bindSet.updateModelToTarget();
}
/**
* Creates a checkbox control with the option given.
*/
protected void createCheckbox(Composite parent, ConfigurationOptionDescription option) {
Button button = toolkit.createButton(parent, getName(option), SWT.CHECK);
GridData layoutData = new GridData();
layoutData.horizontalSpan = 2;
button.setLayoutData(layoutData);
IObservableValue modelv = model.observeEntry(option);
ISWTObservableValue widget = SWTObservables.observeSelection(button);
bindingContext.bindValue(widget, modelv,
new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE),
new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE));
modelv.addChangeListener(new DirtyMarker());
}
protected String getName(ConfigurationOptionDescription option) {
return option.getName();
}
/**
* Creates a drop-down combo with the option given.
*/
protected void createCombo(Composite parent, ConfigurationOptionDescription option) {
Label label = createLabel(toolkit, parent, option);
label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
Combo combo = new Combo(parent, SWT.READ_ONLY);
combo.setItems(option.getValueOptions().toArray(new String[option.getValueOptions().size()]));
IObservableValue modelv = model.observeEntry(option);
ISWTObservableValue widget = SWTObservables.observeSelection(combo);
parentEditor.bindingContext.bindValue(widget, modelv,
new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE),
new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE));
modelv.addChangeListener(new DirtyMarker());
}
/**
* Creates a text field and label combo using the option given.
*/
protected void createTextField(Composite parent, ConfigurationOptionDescription option) {
Label label = createLabel(toolkit, parent, option);
label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
Text text = toolkit.createText(parent, "");
layoutTextField(text);
IObservableValue modelv = model.observeEntry(option);
ISWTObservableValue widget = SWTObservables.observeText(text, SWT.Modify);
bindingContext.bindValue(widget, modelv,
new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE),
new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE));
modelv.addChangeListener(new DirtyMarker());
ChainValidator<String> validationStatusProvider = new ChainValidator<String>(widget,
new ConfigurationSettingValidator(option));
bindingContext.addValidationStatusProvider(validationStatusProvider);
ControlDecoration decoration = new ControlDecoration(text, SWT.TOP | SWT.LEFT);
decoration.setDescriptionText("Invalid value");
FieldDecoration fieldDecoration = FieldDecorationRegistry.getDefault().getFieldDecoration(
FieldDecorationRegistry.DEC_ERROR);
decoration.setImage(fieldDecoration.getImage());
new DecorationChangeListener(decoration, validationStatusProvider.getValidationStatus());
}
protected void layoutTextField(Text text) {
GridData textLayout = new GridData(SWT.LEFT, SWT.TOP, false, false);
GC gc = new GC(text);
FontMetrics fm = gc.getFontMetrics();
textLayout.widthHint = text.computeSize(fm.getAverageCharWidth() * 30, SWT.DEFAULT).x;
gc.dispose();
text.setLayoutData(textLayout);
}
protected Label createLabel(FormToolkit toolkit, Composite parent, ConfigurationOptionDescription option) {
String labelText = getName(option);
if ( option.getChangeSeverity().equals("RestartEnvironment") )
labelText += " **";
else if ( option.getChangeSeverity().equals("RestartApplicationServer") )
labelText += " *";
if ( option.getValueType().equals("CommaSeparatedList") && option.getValueOptions().isEmpty() ) {
labelText += "\n(comma separated)";
} else if ( option.getValueType().equals("KeyValueList") && option.getValueOptions().isEmpty() ) {
labelText += "\n(key-value list)";
}
Label label = toolkit.createLabel(parent, labelText);
return label;
}
/**
* Generic listener that marks the editor dirty.
*/
protected final class DirtyMarker implements SelectionListener, ModifyListener, IChangeListener {
public DirtyMarker() {
}
public void modifyText(ModifyEvent e) {
markDirty();
}
public void widgetSelected(SelectionEvent e) {
markDirty();
}
public void widgetDefaultSelected(SelectionEvent e) {
markDirty();
}
private void markDirty() {
EnvironmentConfigEditorSection.this.parentEditor.markDirty();
}
public void handleChange(ChangeEvent event) {
markDirty();
}
}
}