/*******************************************************************************
* 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.propertypages;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.window.Window;
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.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.FontMetrics;
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.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import de.innot.avreclipse.core.avrdude.ProgrammerConfig;
import de.innot.avreclipse.core.properties.AVRDudeProperties;
import de.innot.avreclipse.ui.preferences.AVRDudeConfigEditor;
/**
* The main / general AVRDude options tab.
* <p>
* On this tab, the following properties are edited:
* <ul>
* <li>Avrdude Programmer Configuration, incl. buttons to edit the current
* config or add a new config</li>
* <li>The JTAG BitClock</li>
* <li>The BitBanger bit change delay</li>
* </ul>
* </p>
*
* @author Thomas Holland
* @since 2.2
*
*/
public class TabAVRDudeProgrammer extends AbstractAVRDudePropertyTab {
// The GUI texts
// Programmer config selection group
private final static String GROUP_PROGCONFIG = "Programmer configuration";
private final static String TEXT_EDITBUTTON = "Edit...";
private final static String TEXT_NEWBUTTON = "New...";
private final static String LABEL_CONFIG_WARNING = "The Programmer configuration previously associated with this project/configuration\n"
+ "does not exist anymore. Please select a different one.";
private final static String LABEL_NOCONFIG = "Please select a Programmer Configuration to enable avrdude functions";
// JTAG Bitclock group
private final static String GROUP_BITCLOCK = "JTAG ICE BitClock";
private final static String LABEL_BITCLOCK = "Specify the bit clock period in microseconds for the JTAG interface or the ISP clock (JTAG ICE only).\n"
+ "Set this to > 1.0 for target MCUs running with less than 4MHz on a JTAG ICE.\n"
+ "Leave the field empty to use the preset bit clock period of the selected Programmer.";
private final static String TEXT_BITCLOCK = "JTAG ICE bitclock";
private final static String LABEL_BITCLOCK_UNIT = "\u03bcs";
// BitBang delay group
private final static String GROUP_DELAY = "BitBang Programmer Bit State Change Delay";
private final static String LABEL_DELAY = "Specify the delay in microseconds for each bit change on bitbang-type programmers.\n"
+ "Set this when the the host system is very fast, or the target runs off a slow clock\n"
+ "Leave the field empty to run the ISP connection at max speed.";
private final static String TEXT_DELAY = "Bit state change delay";
private final static String LABEL_DELAY_UNIT = "\u03bcs";
// The GUI widgets
private Combo fProgrammerCombo;
private Label fConfigWarningIcon;
private Label fConfigWarningMessage;
private Text fBitClockText;
private Text fBitBangDelayText;
/** The Properties that this page works with */
private AVRDudeProperties fTargetProps;
/** Warning image used for invalid Programmer Config values */
private static final Image IMG_WARN = PlatformUI.getWorkbench().getSharedImages().getImage(
ISharedImages.IMG_OBJS_WARN_TSK);
/*
* (non-Javadoc)
*
* @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#createControls(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createControls(Composite parent) {
parent.setLayout(new GridLayout(1, false));
addProgrammerConfigSection(parent);
addBitClockSection(parent);
addBitBangDelaySection(parent);
}
/**
* Add the Programmer Configuration selection <code>Combo</code> and the
* "Edit", "New" Buttons.
*
* @param parent
* <code>Composite</code>
*/
private void addProgrammerConfigSection(Composite parent) {
Group configgroup = setupGroup(parent, GROUP_PROGCONFIG, 3, SWT.NONE);
configgroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
fProgrammerCombo = new Combo(configgroup, SWT.READ_ONLY);
fProgrammerCombo.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
fProgrammerCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
String selectedname = fProgrammerCombo
.getItem(fProgrammerCombo.getSelectionIndex());
String selectedid = getProgrammerConfigId(selectedname);
fTargetProps.setProgrammerId(selectedid);
showProgrammerWarning("", false);
updateAVRDudePreview(fTargetProps);
}
});
// Init the combo with the list of available programmer configurations
loadProgrammerConfigs();
// Edit... Button
Button editButton = setupButton(configgroup, TEXT_EDITBUTTON, 1, SWT.NONE);
editButton.setBackground(parent.getBackground());
editButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
editButtonAction(false);
}
});
// New... Button
Button newButton = setupButton(configgroup, TEXT_NEWBUTTON, 1, SWT.NONE);
newButton.setBackground(parent.getBackground());
newButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
editButtonAction(true);
}
});
// The Warning icon / message composite
Composite warningComposite = new Composite(configgroup, SWT.NONE);
warningComposite.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 3, 1));
GridLayout gl = new GridLayout(2, false);
gl.marginHeight = 0;
gl.marginWidth = 0;
gl.verticalSpacing = 0;
gl.horizontalSpacing = 0;
warningComposite.setLayout(gl);
fConfigWarningIcon = new Label(warningComposite, SWT.LEFT);
fConfigWarningIcon.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));
fConfigWarningIcon.setImage(IMG_WARN);
fConfigWarningMessage = new Label(warningComposite, SWT.LEFT);
fConfigWarningMessage.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
fConfigWarningMessage.setText("two-line\ndummy");
// By default make the warning invisible
// updateData() will make it visible when required
fConfigWarningIcon.setVisible(false);
fConfigWarningMessage.setVisible(false);
}
/**
* The JTAG bitclock section.
* <p>
* The primary control in this section is a text field, that accepts only
* floating point numbers.
* </p>
*
* @param parent
* <code>Composite</code>
*/
private void addBitClockSection(Composite parent) {
// TODO this could be replaced by a combo to select standard values.
// Also this could be implemented as a frequency selector like in AVR
// Studio
// However, investigations into the avrdude source code indicate, that
// the different programmer backends interpret this number differently.
// Especially the stk500v2 backend interprets this number relative to the
// clock frequency of the stk500 programmer.
Group group = new Group(parent, SWT.NONE);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
group.setLayout(new GridLayout(3, false));
group.setText(GROUP_BITCLOCK);
Label label = new Label(group, SWT.WRAP);
label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
label.setText(LABEL_BITCLOCK);
setupLabel(group, TEXT_BITCLOCK, 1, SWT.NONE);
fBitClockText = new Text(group, SWT.BORDER | SWT.RIGHT);
GridData gd = new GridData(SWT.FILL, SWT.FILL, false, false);
FontMetrics fm = getFontMetrics(parent);
gd.widthHint = Dialog.convertWidthInCharsToPixels(fm, 12);
fBitClockText.setLayoutData(gd);
fBitClockText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
fTargetProps.setBitclock(fBitClockText.getText());
updateAVRDudePreview(fTargetProps);
}
});
fBitClockText.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent event) {
// Accept only digits and -at most- one dot '.'
int dotcount = 0;
if (fBitClockText.getText().contains(".")) {
dotcount++;
}
String text = event.text;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (ch == '.') {
dotcount++;
if (dotcount > 1) {
event.doit = false;
return;
}
} else if (!('0' <= ch && ch <= '9')) {
event.doit = false;
return;
}
}
}
});
// Label with the units (microseconds)
setupLabel(group, LABEL_BITCLOCK_UNIT, 1, SWT.FILL);
}
/**
* The BitBang bit change delay section.
* <p>
* The primary control in this section is a text field, that accepts only
* integers.
* </p>
*
* @param parent
* <code>Composite</code>
*/
private void addBitBangDelaySection(Composite parent) {
Group group = new Group(parent, SWT.NONE);
group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
group.setLayout(new GridLayout(3, false));
group.setText(GROUP_DELAY);
Label label = new Label(group, SWT.WRAP);
label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
label.setText(LABEL_DELAY);
setupLabel(group, TEXT_DELAY, 1, SWT.NONE);
fBitBangDelayText = new Text(group, SWT.BORDER | SWT.RIGHT);
GridData gd = new GridData(SWT.FILL, SWT.FILL, false, false);
FontMetrics fm = getFontMetrics(parent);
gd.widthHint = Dialog.convertWidthInCharsToPixels(fm, 12);
fBitBangDelayText.setLayoutData(gd);
fBitBangDelayText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
fTargetProps.setBitBangDelay(fBitBangDelayText.getText());
updateAVRDudePreview(fTargetProps);
}
});
fBitBangDelayText.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent event) {
// Accept only digits
String text = event.text;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
if (!('0' <= ch && ch <= '9')) {
event.doit = false;
return;
}
}
}
});
// Label with the units (microseconds)
setupLabel(group, LABEL_DELAY_UNIT, 1, SWT.FILL);
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.propertypages.AbstractAVRPropertyTab#performApply(de.innot.avreclipse.core.preferences.AVRProjectProperties)
*/
@Override
protected void performApply(AVRDudeProperties dstprops) {
// Save all new / modified programmer configurations
saveProgrammerConfigs();
// Copy the currently selected values of this tab to the given, fresh
// Properties.
// The caller of this method will handle the actual saving
dstprops.setProgrammerId(fTargetProps.getProgrammerId());
dstprops.setBitclock(fTargetProps.getBitclock());
dstprops.setBitBangDelay(fTargetProps.getBitBangDelay());
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.propertypages.AbstractAVRPropertyTab#performDefaults()
*/
@Override
protected void performDefaults() {
// Reset the list of Programmer Configurations
loadProgrammerConfigs();
// The other defaults related stuff is done in the performCopy() method,
// which is called later by the superclass.
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.propertypages.AbstractAVRPropertyTab#performDefaults(de.innot.avreclipse.core.preferences.AVRProjectProperties)
*/
@Override
protected void performCopy(AVRDudeProperties srcprops) {
// Reload the items on this page
fTargetProps.setProgrammerId(srcprops.getProgrammerId());
fTargetProps.setBitclock(srcprops.getBitclock());
fTargetProps.setBitBangDelay(srcprops.getBitBangDelay());
updateData(fTargetProps);
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.propertypages.AbstractAVRPropertyTab#updateData(de.innot.avreclipse.core.preferences.AVRProjectProperties)
*/
@Override
protected void updateData(AVRDudeProperties props) {
fTargetProps = props;
// Set the selection of the Programmercombo
// If the programmerid of the target properties does not exist,
// show a warning and select the first item (without copying it into the
// Target Properties)
String programmerid = fTargetProps.getProgrammerId();
if (programmerid.length() == 0) {
// No Programmer has been set yet
// Deselect the combo and show a Message
fProgrammerCombo.deselect(fProgrammerCombo.getSelectionIndex());
showProgrammerWarning(LABEL_NOCONFIG, false);
} else {
// Programmer id exists. Now test if it is still valid
if (!isValidId(programmerid)) {
// id is not valid. Deselect Combo and show a Warning
fProgrammerCombo.deselect(fProgrammerCombo.getSelectionIndex());
showProgrammerWarning(LABEL_CONFIG_WARNING, true);
} else {
// everything is good. Select the id in the combo
String programmername = getProgrammerConfigName(programmerid);
int index = fProgrammerCombo.indexOf(programmername);
fProgrammerCombo.select(index);
showProgrammerWarning("", false);
}
}
fBitClockText.setText(fTargetProps.getBitclock());
fBitBangDelayText.setText(fTargetProps.getBitBangDelay());
}
/*
* (non-Javadoc)
*
* @see de.innot.avreclipse.ui.propertypages.AbstractAVRDudePropertyTab#doProgConfigsChanged(java.lang.String[],
* int)
*/
@Override
protected void doProgConfigsChanged(String[] configs, int newindex) {
fProgrammerCombo.setItems(configs);
// make the combo show all available items (no scrollbar)
fProgrammerCombo.setVisibleItemCount(configs.length);
if (newindex != -1) {
fProgrammerCombo.select(newindex);
} else {
fProgrammerCombo.deselect(fProgrammerCombo.getSelectionIndex());
}
};
/**
* Adds a new configuration or edits the currently selected Programmer
* Configuration.
* <p>
* Called when either the new or the edit button has been clicked.
* </p>
*
* @see AVRDudeConfigEditor
*/
private void editButtonAction(boolean createnew) {
ProgrammerConfig oldconfig = null;
// Create a list of all currently available configurations
// This is used by the editor to avoid name clashes
// (a configuration name needs to be unique)
String[] allcfgs = fProgrammerCombo.getItems();
Set<String> allconfignames = new HashSet<String>(allcfgs.length);
for (String cfg : allcfgs) {
allconfignames.add(cfg);
}
if (createnew) { // new config
// Create a new configuration with a default name
// (with a trailing running number if required),
// a sample Description text and stk500v2 as programmer
// (because I happen to have one)
// All other options remain at the default (empty)
String basename = "New Configuration";
String defaultname = basename;
int i = 1;
while (allconfignames.contains(defaultname)) {
defaultname = basename + " (" + i++ + ")";
}
oldconfig = fCfgManager.createNewConfig();
oldconfig.setName(defaultname);
} else { // edit existing config
// Get the ProgrammerConfig from the Combo
String configname = allcfgs[fProgrammerCombo.getSelectionIndex()];
String configid = getProgrammerConfigId(configname);
oldconfig = getProgrammerConfig(configid);
}
// Open the Config Editor.
// If the OK Button was selected, the modified Config is fetched from
// the Dialog and the the superclass is informed about the addition /
// modification.
AVRDudeConfigEditor dialog = new AVRDudeConfigEditor(fProgrammerCombo.getShell(),
oldconfig, allconfignames);
if (dialog.open() == Window.OK) {
// OK Button selected:
ProgrammerConfig newconfig = dialog.getResult();
fTargetProps.setProgrammer(newconfig);
addProgrammerConfig(newconfig);
updateData(fTargetProps.getParent());
}
}
/**
* Show the supplied Warning in the Programmer config group.
*
* @param text
* Message to display.
* @param warning
* <code>true</code> to make the warning visible,
* <code>false</code> to hide it.
*/
private void showProgrammerWarning(String text, boolean warning) {
fConfigWarningIcon.setVisible(warning);
fConfigWarningMessage.setText(text);
fConfigWarningMessage.pack();
fConfigWarningMessage.setVisible(true);
}
}