/*******************************************************************************
* Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) and others.
* 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:
* Thomas Holland - initial API and implementation
*******************************************************************************/
package de.innot.avreclipse.ui.editors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IFormPart;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.SectionPart;
import org.eclipse.ui.forms.widgets.ColumnLayoutData;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import de.innot.avreclipse.core.toolinfo.fuses.BitFieldDescription;
import de.innot.avreclipse.core.toolinfo.fuses.BitFieldValueDescription;
import de.innot.avreclipse.core.toolinfo.fuses.ByteValueChangeEvent;
import de.innot.avreclipse.core.toolinfo.fuses.ByteValues;
import de.innot.avreclipse.core.toolinfo.fuses.IByteValuesChangeListener;
/**
* A <code>SectionPart</code> that can edit a single BitField.
* <p>
* This class takes a JFace Form Section and adds the Widgets needed to edit the value of a
* BitField. Depending on the size of the BitField and the possible values from the
* {@link BitFieldDescription} (derived from the part description files), the Section will use
* different representations.
* <ul>
* <li>Single bit
* <p>
* <ul>
* <li>no predefined values: <em>Yes</em> and <em>No</em> Radiobuttons.</li>
* <li>Single bit, one predefined value: Checkbox.</li>
* </ul>
* </p>
* <li>Multiple bits
* <p>
* <ul>
* <li>no predefined values: Textbox for direct user input (decimal or hexadecimal).</li>
* <li>up to 6 predefined values: Radiobuttons for all values.</li>
* <li>up to 16 predefined values: Single drop down Combo.</li>
* <li>more than 16 predefined values: Two drop down Combos, values split at first ";" character.</li>
* </ul>
* </p>
* </ul>
* </p>
* <p>
* This class automatically creates a <code>Section</code> and adds it to the parent composite.
* After the class has been instantiated it must be added to a <code>IManagedForm</code> to
* participate in the lifecycle management of the managed form.
*
* <pre>
* Composite parent = ...
* FormToolkit toolkit = ...
* IManagedForm managedForm = ...
* BitFieldDescription bfd = ...
*
* IFormPart part = new BitFieldEditorSectionPart(parent, toolkit, Section.TITLE_BAR, bfd);
* managedForm.addPart(part);
* </pre>
*
* </p>
* <p>
* This class implements the {@link IFormPart} interface to participate in the lifecycle management
* of a managed form. To set the value of the BitField use
*
* <pre>
* ByteValues bytevalues = ...
* managedForm.setInput(bytevalues);
* </pre>
*
* The <code>ByteValues</code> passed to the managedForm is the model for this
* <code>SectionPart</code>. Unlike normal IFormParts all changes to the source ByteValues are
* applied immediately, because other BitFields or even other editors might be affected by the
* change. Therefore this class uses its own dirty / stale management and does not use the one
* provided by the superclass {@link SectionPart}.
* </p>
* <p>
* This part also adds itself as a listener for changes to the ByteValues model. If the BitField
* managed by this section gets changed from outside, then this part is marked as stale. The new
* value will be set during the refresh method.
*
* </p>
*
* @author Thomas Holland
* @since 2.3
*
*/
public class BitFieldEditorSectionPart extends SectionPart implements IByteValuesChangeListener {
/** The BitField this Section should take care of. */
private final BitFieldDescription fBFD;
/**
* The model for this <code>SectionPart</code>. Will only be written to in the
* {@link #commit(boolean)} method.
*/
private ByteValues fByteValues;
/** Last clean BitField value. Used to check if this part is currently dirty. */
private int fLastCleanValue = -1;
/** Current BitField value. Used to check if this part is currently stale. */
private int fCurrentValue = -1;
/**
* Part is currently committing the value. Used to inhibit reacting to ByteValueChangeEvents
* caused by this class.
*/
private boolean fInCommit = false;
/** Part is currently refreshing. Used to inhibit any modification listeners. */
private boolean fInRefresh = false;
/**
* The Control that handles the different visual representations.
*
* @see IOptionPart
*/
private IOptionPart fOptionPart;
/**
* Create a new <code>SectionPart</code> to handle a single BitField.
* <p>
* This constructor automatically creates a new section part inside the provided parent and
* using the provided toolkit.
* </p>
*
* @param parent
* the parent
* @param toolkit
* the toolkit to use
* @param style
* the section widget style
* @param description
* <code>BitFieldDescription</code> for the BitField.
*/
public BitFieldEditorSectionPart(Composite parent, FormToolkit toolkit, int style,
BitFieldDescription description) {
super(parent, toolkit, style);
fBFD = description;
getSection().setLayoutData(new ColumnLayoutData(200, SWT.DEFAULT));
getSection().setText(fBFD.getName() + " - " + fBFD.getDescription());
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.AbstractFormPart#initialize(org.eclipse.ui.forms.IManagedForm)
*/
@Override
public void initialize(IManagedForm form) {
super.initialize(form);
Section parent = getSection();
FormToolkit toolkit = form.getToolkit();
// Create the Section client area.
// The layout of the client area is set later in the individual IOptionPart classes
Composite clientarea = form.getToolkit().createComposite(parent);
parent.setClient(clientarea);
// Determine the number of possible options and the list of possible values.
// Then create a new IOptionPart according to the rules described in the class JavaDoc.
int maxoptions = fBFD.getMaxValue();
List<BitFieldValueDescription> allvalues = fBFD.getValuesEnumeration();
if (maxoptions == 1 && allvalues.size() == 0) {
fOptionPart = new OptionYesNo();
} else if (maxoptions == 1) {
fOptionPart = new OptionCheckbox();
} else if (/* maxoptions > 1 && */allvalues.size() == 0) {
fOptionPart = new OptionText();
} else if (/* maxoptions > 1 && */allvalues.size() < 6) {
fOptionPart = new OptionRadioButtons();
} else {
// Check if all possible values are splitable. It is splitable if
// it contains (at least) one ';'
// While at the time it would be enough to just check the number of values this more
// expensive check is here to ensure that even future MCUs will not cause problems.
// (OptionDualCombo will probably fail if even a single value has no ';' in it.)
boolean splitable = true;
for (BitFieldValueDescription bfvd : allvalues) {
if (bfvd.getDescription().indexOf(';') == -1) {
splitable = false;
break;
}
}
if (splitable && allvalues.size() > 16) {
fOptionPart = new OptionDualCombo();
} else {
fOptionPart = new OptionSingleCombo();
}
}
fOptionPart.addControl(clientarea, toolkit, fBFD);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.AbstractFormPart#dispose()
*/
@Override
public void dispose() {
if (fByteValues != null) {
fByteValues.removeChangeListener(this);
}
super.dispose();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.AbstractFormPart#setFormInput(java.lang.Object)
*/
@Override
public boolean setFormInput(Object input) {
if (!(input instanceof ByteValues)) {
return false;
}
fByteValues = (ByteValues) input;
fByteValues.addChangeListener(this);
refresh();
return true;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.AbstractFormPart#refresh()
*/
@Override
public void refresh() {
// refresh() was once called before setInput(), but I am not sure if this was a bug in the
// Editor. I have left this test just in case, even if it is probably not required.
if (fByteValues == null) {
return;
}
int value = fByteValues.getNamedValue(fBFD.getName());
fInRefresh = true;
fOptionPart.setValue(value);
fInRefresh = false;
fLastCleanValue = fCurrentValue = value;
super.refresh();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.AbstractFormPart#commit(boolean)
*/
@Override
public void commit(boolean onSave) {
fLastCleanValue = fCurrentValue = fByteValues.getNamedValue(fBFD.getName());
super.commit(onSave);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.AbstractFormPart#isDirty()
*/
@Override
public boolean isDirty() {
// This part is dirty if the source ByteValues has a different value than what it had on the
// last setInput(), refresh() or commit()
try {
if (fByteValues.getNamedValue(fBFD.getName()) != fLastCleanValue) {
return true;
}
} catch (IllegalArgumentException iae) {
// The ByteValues source has changed (but our parent editorpart has not yet received
// news about this)
// In this case we consider ourself to be clean as this section will be
// disposed of shortly.
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.AbstractFormPart#isStale()
*/
@Override
public boolean isStale() {
// This part is stale if the source ByteValues has a different value than what this part
// thinks it should have.
if (fByteValues.getNamedValue(fBFD.getName()) != fCurrentValue) {
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.core.toolinfo.fuses.IByteValuesChangeListener#byteValuesChanged(de.innot.avreclipse.core.toolinfo.fuses.ByteValueChangeEvent[])
*/
public void byteValuesChanged(ByteValueChangeEvent[] events) {
if (fInCommit) {
// don't listen to our own changes to the BitField
return;
}
// go through all events and if any event changes our
// BitField to a different value then mark ourself as (probably) stale.
for (ByteValueChangeEvent event : events) {
if (event.name.equals(fBFD.getName())) {
// Our stale state might have changed, depending on the new value.
// Inform the parent ManagedForm - it will call our isStale() implementation to get
// the actual state.
getManagedForm().staleStateChanged();
}
}
}
/**
* Sets the value of this Section part.
* <p>
* This is called by the <code>IOptionPart</code>s when the user has changed the selection.
* The new value is applied immediately to the model and the parent IManagedForm is informed
* that the dirty state might have changed.
* </p>
*
* @param newvalue
* The new value from the user selection.
*/
private void internalSetValue(int newvalue) {
fInCommit = true;
fByteValues.setNamedValue(fBFD.getName(), newvalue);
fInCommit = false;
fCurrentValue = newvalue;
// Our dirty state might have changed, depending on the new value.
// Inform the parent ManagedForm - it will call our isDirty() implementation to get the
// actual state.
getManagedForm().dirtyStateChanged();
}
/**
* A simple interface to abstract the different visual representations of a BitField.
* <p>
* All six different presentations are implemented as classes implementing this interface. The
* parent class will instantiate one of them and then use the interface to initialize them
* (generate the UI) and to set their value.
* </p>
*/
private interface IOptionPart {
/**
* Generate a user interface for a <code>BitFieldDescription</code>.
*
* @param parent
* Composite to which the generated Widgets are added. Layout will be set by this
* method.
* @param toolkit
* for the visual style of the form.
* @param bfd
* source BitFieldDescription.
*/
public void addControl(Composite parent, FormToolkit toolkit, BitFieldDescription bfd);
/**
* Set the new BitField value.
* <p>
* The OptionPart will update itself to show the new value. The new value may be
* <code>-1</code>. The implementation should try to visualize this if possible.
* </p>
*
* @param value
* <code>int</code> between <code>0</code> and
* {@link BitFieldDescription#getMaxValue()}. The value is not checked. Illegal
* values may cause undetermined behaviour.
*/
public void setValue(int value);
}
/**
* <code>IOptionPart</code> to represent a single bit as "Yes" / "No" Radio buttons.
* <p>
* Note: a <code>0</code> is interpreted as <em>Yes</em>, while a <code>1</code> is
* <em>No</em>. This is according to the Atmel definition for fuses and them being flash
* memory bits, where unset means <code>1</code>.
* </p>
*/
private class OptionYesNo implements IOptionPart {
private Button fYesButton;
private Button fNoButton;
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#addControl(org.eclipse.swt.widgets.Composite,
* org.eclipse.ui.forms.widgets.FormToolkit,
* de.innot.avreclipse.core.toolinfo.fuses.BitFieldDescription)
*/
public void addControl(Composite parent, FormToolkit toolkit, BitFieldDescription bfd) {
parent.setLayout(new GridLayout(2, false));
fYesButton = toolkit.createButton(parent, "Yes", SWT.RADIO);
fNoButton = toolkit.createButton(parent, "No", SWT.RADIO);
Listener listener = new Listener() {
public void handleEvent(Event event) {
if (fInRefresh) {
// don't listen to events originating from refresh()
return;
}
int value = (event.widget == fYesButton) ? 0x00 : 0x01;
internalSetValue(value);
}
};
fYesButton.addListener(SWT.Selection, listener);
fNoButton.addListener(SWT.Selection, listener);
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#setValue(int)
*/
public void setValue(int value) {
if (value == -1) {
fYesButton.setSelection(false);
fNoButton.setSelection(false);
} else {
boolean valueYes = value == 0 ? true : false;
fYesButton.setSelection(valueYes);
fNoButton.setSelection(!valueYes);
}
}
}
/**
* <code>IOptionPart</code> to represent a single bit with one possible option as a CheckBox
* button.
* <p>
* While rare, some fuses descriptions contain a single bit with a single value. If the CheckBox
* is selected, then the BitField value is set to the single value. If the CheckBox is not
* selected, the negation of the value is used.
* </p>
*/
private class OptionCheckbox implements IOptionPart {
private Button fCheckButton;
private int fSetValue;
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#addControl(org.eclipse.swt.widgets.Composite,
* org.eclipse.ui.forms.widgets.FormToolkit,
* de.innot.avreclipse.core.toolinfo.fuses.BitFieldDescription)
*/
public void addControl(Composite parent, FormToolkit toolkit, BitFieldDescription bfd) {
parent.setLayout(new FillLayout());
fSetValue = bfd.getValuesEnumeration().get(0).getValue();
String buttontext = bfd.getValuesEnumeration().get(0).getDescription();
fCheckButton = toolkit.createButton(parent, buttontext, SWT.CHECK);
Listener listener = new Listener() {
public void handleEvent(Event event) {
if (fInRefresh) {
// don't listen to events originating from refresh()
return;
}
int value = fCheckButton.getSelection() ? fSetValue : 0x01 & ~fSetValue;
internalSetValue(value);
}
};
fCheckButton.addListener(SWT.Selection, listener);
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#setValue(int)
*/
public void setValue(int value) {
if (value == -1) {
fCheckButton.setGrayed(true);
} else {
fCheckButton.setGrayed(false);
boolean valueYes = value == fSetValue ? true : false;
fCheckButton.setSelection(valueYes);
}
}
}
/**
* <code>IOptionPart</code> to represent all legal values for the BitField as radio buttons.
*/
private class OptionRadioButtons implements IOptionPart {
private Button[] fButtons;
private int[] fValues;
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#addControl(org.eclipse.swt.widgets.Composite,
* org.eclipse.ui.forms.widgets.FormToolkit,
* de.innot.avreclipse.core.toolinfo.fuses.BitFieldDescription)
*/
public void addControl(Composite parent, FormToolkit toolkit, BitFieldDescription bfd) {
parent.setLayout(new RowLayout(SWT.VERTICAL));
List<BitFieldValueDescription> allvalues = bfd.getValuesEnumeration();
fButtons = new Button[allvalues.size()];
fValues = new int[allvalues.size()];
Listener listener = new Listener() {
public void handleEvent(Event event) {
if (fInRefresh) {
// don't listen to events originating from refresh()
return;
}
int value = -1;
for (int i = 0; i < fButtons.length; i++) {
if (fButtons[i] == event.widget) {
value = fValues[i];
}
}
internalSetValue(value);
}
};
for (int i = 0; i < allvalues.size(); i++) {
BitFieldValueDescription bfvd = allvalues.get(i);
fButtons[i] = toolkit.createButton(parent, bfvd.getDescription(), SWT.RADIO);
fButtons[i].addListener(SWT.Selection, listener);
fValues[i] = bfvd.getValue();
}
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#setValue(int)
*/
public void setValue(int value) {
for (int i = 0; i < fValues.length; i++) {
if (fValues[i] == value) {
fButtons[i].setSelection(true);
} else {
fButtons[i].setSelection(false);
}
}
}
}
/**
* <code>IOptionPart</code> to represent all legal values for the BitField as two combos.
* <p>
* Two combos are used to split very long lists of values into two more managable lists. All
* values are split at the first ';' character in their description and group according to the
* first part of the description.
* </p>
* <p>
* Whenever the first root combo changes its value, the second combo is filled with the
* appropriate subitems.
* </p>
*/
private class OptionDualCombo implements IOptionPart {
private Combo fRootCombo;
private Combo fSubCombo;
/**
* The list of root names, once filled this is static and used as the content for the root
* combo.
*/
private String[] fRootTexts;
/**
* Map for all possible values to the index of the root and the sub combo. For undefined
* values this will map to <code>{-1,-1}</code>.
*/
private int[][] fReverseLookup;
/**
* Map of root names to a list of all possible sub names. This list is used to fill the sub
* combo.
*/
private final Map<String, List<String>> fRootToSubnames = new HashMap<String, List<String>>();
/**
* Map of root names to a list of values. The list is contains the value associated for the
* sub name at the same index.
*/
private final Map<String, List<Integer>> fRootToValues = new HashMap<String, List<Integer>>();
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#addControl(org.eclipse.swt.widgets.Composite,
* org.eclipse.ui.forms.widgets.FormToolkit,
* de.innot.avreclipse.core.toolinfo.fuses.BitFieldDescription)
*/
public void addControl(Composite parent, FormToolkit toolkit, BitFieldDescription bfd) {
parent.setLayout(new GridLayout(2, false));
List<BitFieldValueDescription> allvalues = bfd.getValuesEnumeration();
// initialize the reverse lookup array and fill it with {-1, -1} to indicate
// undefined BitField values.
int maxvalue = bfd.getMaxValue();
fReverseLookup = new int[maxvalue + 1][];
Arrays.fill(fReverseLookup, new int[] { -1, -1 });
List<String> rootnames = new ArrayList<String>();
// iterate over all BitFieldValueDescriptions and split their names.
// If the first part is new, then add it to the list of rootnames.
// The second part and the values are added to the lists associated with the rootname
// from the two <code>rootTo...</code> HashMaps.
// Finally the value of the BitFieldValueDescription is added to the reverse lookup map
// with the current indices for root and sub combo.
String lastroottext = null;
for (BitFieldValueDescription bfvd : allvalues) {
String desc = bfvd.getDescription();
int value = bfvd.getValue();
// Split the current text
int splitat = desc.indexOf(';');
String firstpart = desc.substring(0, splitat).trim();
if (!firstpart.equalsIgnoreCase(lastroottext)) {
rootnames.add(firstpart);
fRootToSubnames.put(firstpart, new ArrayList<String>());
fRootToValues.put(firstpart, new ArrayList<Integer>());
lastroottext = firstpart;
}
String subtext = desc.substring(splitat + 1).trim();
List<String> subnames = fRootToSubnames.get(firstpart);
subnames.add(subtext);
List<Integer> subvalues = fRootToValues.get(firstpart);
subvalues.add(value);
// Map the index values of both name parts to the value, so we can get the
// indices for a given value.
// As we fill the arrays sequentially we can just take the size of the arrays to
// get the index of the last addition (instead of the more expensive
// list.indexOf())
fReverseLookup[value] = new int[] { rootnames.size() - 1, subnames.size() - 1 };
}
// Convert the root list to an array usable as content for the root combo.
fRootTexts = rootnames.toArray(new String[rootnames.size()]);
// Now create the root combo
fRootCombo = new Combo(parent, SWT.READ_ONLY);
toolkit.adapt(fRootCombo);
fRootCombo.setItems(fRootTexts);
fRootCombo.setVisibleItemCount(fRootTexts.length);
fRootCombo.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
if (fInRefresh) {
// don't listen to events originating from refresh()
return;
}
int index = fRootCombo.getSelectionIndex();
String name = fRootTexts[index];
List<String> subnames = fRootToSubnames.get(name);
String[] subnamesarray = subnames.toArray(new String[subnames.size()]);
// remember the last selection of the sub combo. If it is still in range for the
// new sub combo content then the selection index is reset to it after we have
// set the new content. Often the same index has the same description, so the
// user won't see a change in the sub combo, even though the actual BitField
// values are totally different.
int oldselection = fSubCombo.getSelectionIndex();
fSubCombo.setItems(subnamesarray);
fSubCombo.setVisibleItemCount(subnamesarray.length);
if (0 <= oldselection && oldselection < subnamesarray.length) {
fSubCombo.select(oldselection);
} else {
fSubCombo.select(0);
}
// Because the root combo does not affect the value directly we need to call the
// selection event handler of the sub combo so that internalSetValue() gets
// actually called.
// Combo.select(x) unfortunatly does not do this automatically.
fSubCombo.notifyListeners(SWT.Selection, new Event());
}
});
// ... and now the sub combo
fSubCombo = new Combo(parent, SWT.READ_ONLY);
toolkit.adapt(fSubCombo);
fSubCombo.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
if (fInRefresh) {
// don't listen to events originating from refresh()
return;
}
List<Integer> subvalues;
int rootindex = fRootCombo.getSelectionIndex();
String name = fRootTexts[rootindex];
subvalues = fRootToValues.get(name);
int subindex = fSubCombo.getSelectionIndex();
int newvalue = subvalues.get(subindex);
internalSetValue(newvalue);
}
});
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#setValue(int)
*/
public void setValue(int value) {
if (value == -1) {
fRootCombo.clearSelection();
fSubCombo.clearSelection();
return;
}
int[] indices = fReverseLookup[value];
if (indices[0] == -1) {
// invalid value: deselect both combos
fRootCombo.select(-1);
fSubCombo.select(-1);
return;
}
fRootCombo.select(indices[0]);
String name = fRootTexts[indices[0]];
List<String> subnames = fRootToSubnames.get(name);
String[] subnamesarray = subnames.toArray(new String[subnames.size()]);
fSubCombo.setItems(subnamesarray);
fSubCombo.setVisibleItemCount(subnamesarray.length);
fSubCombo.select(indices[1]);
}
}
/**
* <code>IOptionPart</code> to represent all legal values for the BitField in a drop down
* combo.
*/
private class OptionSingleCombo implements IOptionPart {
private Combo fCombo;
/**
* The list of value names. Once filled this is static and used as the content for the
* combo.
*/
private String[] fTexts;
/**
* List of values. Once filled this is static and used to map the index of the combo to the
* actual BitField value.
*/
private Integer[] fValues;
/**
* Map for all possible values to the index of the combo. For undefined values this will map
* to <code>-1</code>.
*/
private int[] fReverseLookup;
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#addControl(org.eclipse.swt.widgets.Composite,
* org.eclipse.ui.forms.widgets.FormToolkit,
* de.innot.avreclipse.core.toolinfo.fuses.BitFieldDescription)
*/
public void addControl(Composite parent, FormToolkit toolkit, BitFieldDescription bfd) {
parent.setLayout(new RowLayout(SWT.HORIZONTAL));
List<BitFieldValueDescription> allvalues = bfd.getValuesEnumeration();
// initialize the reverse lookup array and fill it with {-1} to indicate
// undefined BitField values.
int maxvalue = bfd.getMaxValue();
fReverseLookup = new int[maxvalue + 1];
Arrays.fill(fReverseLookup, -1);
// Iterate over all BitFieldValueDescriptions and extract name and value.
List<String> names = new ArrayList<String>();
List<Integer> values = new ArrayList<Integer>();
for (BitFieldValueDescription bfvd : allvalues) {
String desc = bfvd.getDescription();
int value = bfvd.getValue();
names.add(desc);
values.add(value);
fReverseLookup[value] = names.size() - 1;
}
// Convert the lists to arrays for easier use with the combo
fTexts = names.toArray(new String[names.size()]);
fValues = values.toArray(new Integer[values.size()]);
// and create the combo
fCombo = new Combo(parent, SWT.READ_ONLY);
toolkit.adapt(fCombo);
fCombo.setItems(fTexts);
fCombo.setVisibleItemCount(fTexts.length);
fCombo.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
if (fInRefresh) {
// don't listen to events originating from refresh()
return;
}
int index = fCombo.getSelectionIndex();
int newvalue = fValues[index];
internalSetValue(newvalue);
}
});
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#setValue(int)
*/
public void setValue(int value) {
if (value == -1) {
fCombo.clearSelection();
fCombo.deselectAll();
return;
}
int index = fReverseLookup[value];
fCombo.select(index);
}
}
/**
* <code>IOptionPart</code> to edit the value of a BitField directly in a text box.
* <p>
* The new value can be entered either as decimal, hexadecimal or (for oldtimers) as octal. The
* new value is decoded by the {@link Integer#decode(String)} method, so all its features and
* restrictions apply to this class.
* </p>
* <p>
* If the entered value is illegal, either out of range or malformed, then the text is shown in
* red.
* </p>
*/
private class OptionText implements IOptionPart {
/**
* Highest integer value that can be entered. Higher values will be ignored and the error is
* visualized.
*/
private int fMaxValue;
private Text fText;
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#addControl(org.eclipse.swt.widgets.Composite,
* org.eclipse.ui.forms.widgets.FormToolkit,
* de.innot.avreclipse.core.toolinfo.fuses.BitFieldDescription)
*/
public void addControl(Composite parent, FormToolkit toolkit, BitFieldDescription bfd) {
parent.setLayout(new RowLayout());
fMaxValue = bfd.getMaxValue();
fText = toolkit.createText(parent, "", SWT.NONE);
fText.setTextLimit(5);
fText.setToolTipText("Decimal, Hexadecimal (0x..) or Octal (0...)");
fText.addListener(SWT.Modify, new Listener() {
// The Modify Listener checks if the value is valid.
// If yes then the value is set in the parent,
// if no then the foreground is colored red.
public void handleEvent(Event event) {
if (fInRefresh) {
// don't listen to events originating from refresh()
return;
}
try {
int value = Integer.decode(fText.getText());
if (value <= fMaxValue) {
fText.setForeground(fText.getDisplay().getSystemColor(SWT.COLOR_BLACK));
internalSetValue(value);
return;
}
} catch (NumberFormatException nfe) {
}
fText.setForeground(fText.getDisplay().getSystemColor(SWT.COLOR_RED));
}
});
fText.addVerifyListener(new VerifyListener() {
// The verify listener to only accept (hex) digits and convert them to
// upper case
public void verifyText(VerifyEvent event) {
String text = event.text.toUpperCase();
text = text.replace('X', 'x');
if (!text.matches("[0-9A-Fx]*")) {
event.doit = false;
}
event.text = text;
}
});
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.editors.BitFieldEditorSectionPart.IOptionPart#setValue(int)
*/
public void setValue(int value) {
if (value == -1) {
fText.setText("");
} else {
fText.setText("0x" + Integer.toHexString(value));
}
}
}
}