/* * 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(); } } }