/******************************************************************************* * 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.preferences; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.StatusDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; 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.events.VerifyEvent; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.layout.FillLayout; 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.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.List; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import de.innot.avreclipse.AVRPlugin; import de.innot.avreclipse.core.avrdude.AVRDudeException; import de.innot.avreclipse.core.avrdude.ProgrammerConfig; import de.innot.avreclipse.core.avrdude.ProgrammerConfigManager; import de.innot.avreclipse.core.targets.IProgrammer; import de.innot.avreclipse.core.toolinfo.AVRDude; import de.innot.avreclipse.core.toolinfo.AVRDude.ConfigEntry; import de.innot.avreclipse.core.toolinfo.AVRDude.ConfigProgrammerEntry; import de.innot.avreclipse.ui.dialogs.AVRDudeErrorDialog; /** * Dialog to edit a AVRDude Programmer Configuration. * <p> * This dialog is self contained and is used to edit a AVRDude Programmer configuration. Only the * AVRDude options specific to a programmer are included, as defined in the {@link ProgrammerConfig} * class. * </p> * * @author Thomas Holland * @since 2.2 * @since 2.3 Added optional post avrdude invocation delay * */ public class AVRDudeConfigEditor extends StatusDialog { /** The working copy of the given source Configuration */ private final ProgrammerConfig fConfig; /** Map of all Programmer IDs to their ConfigEntry. */ private Map<String, IProgrammer> fConfigIDMap; /** Map of all Programmer names to their ConfigEntry */ private Map<String, IProgrammer> fConfigNameMap; /** * List of all existing configurations to avoid duplicate names (configuration names need to be * unique) */ private final Set<String> fAllConfigs; private Text fPreviewText; /** * Constructor for a new Configuration Editor. * <p> * The passed <code>ProgrammerConfig</code> is copied and not touched. The modified * <code>ProgrammerConfig</code> can be retrieved with the {@link #getResult()} method. * </p> * <p> * The Set of all known configurations is required to prevent duplicate names. * </p> * * @param parent * Parent <code>Shell</code> * @param config * The <code>ProgrammerConfig</code> to edit. It is copied and not modified directly. * @param allconfigs * A <code>Set<String></code> of all known configuration names. */ /** * @param parent */ public AVRDudeConfigEditor(Shell parent, ProgrammerConfig config, Set<String> allconfigs) { super(parent); setTitle("Edit AVRDude Programmer Configuration " + config.getName()); // Allow this dialog to be resizeable setShellStyle(getShellStyle() | SWT.RESIZE); // make a copy of the given Configuration that we can modify as required fConfig = ProgrammerConfigManager.getDefault().getConfigEditable(config); // Remove the current name from the list of all names fAllConfigs = allconfigs; if (fAllConfigs.contains(fConfig.getName())) { fAllConfigs.remove(fConfig.getName()); } try { // Get the List of AVRDude Programmer ConfigEntries. // They are used to build the List of Programmers and to show // details of // a selected programmer Collection<IProgrammer> programmers = AVRDude.getDefault().getProgrammersList(); fConfigIDMap = new HashMap<String, IProgrammer>(programmers.size()); fConfigNameMap = new HashMap<String, IProgrammer>(programmers.size()); for (IProgrammer type : programmers) { fConfigIDMap.put(type.getId(), type); if (type.hasParent()) { // We need a unique string and the parent may have the same description fConfigNameMap.put(type.getDescription() + " (" + type.getId() + ")", type); } else { fConfigNameMap.put(type.getDescription(), type); } } } catch (AVRDudeException e) { AVRDudeErrorDialog.openAVRDudeError(getShell(), e, null); } } /* * (non-Javadoc) * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) */ @Override protected Control createDialogArea(Composite parent) { // Create the actual layout with all controls in a three column Layout. // The third column is currently unused, but may be used in the future // to add a "Browse..." Button to the Port option. Composite composite = (Composite) super.createDialogArea(parent); composite.setLayout(new GridLayout(3, false)); addNameControl(composite); addDescriptionControl(composite); addProgrammersComposite(composite); addPortControl(composite); addBaudrateControl(composite); addOtherOptionsComposite(composite); addExitspecComposite(composite); addPostAVRDudeDelayControl(composite); addCommandlinePreview(composite); updateCommandPreview(); return composite; } /** * Adds the configuration name control. * <p> * This control edits the <code>ProgrammerConfig<code> name property. * </p> * <p> * The entered name is checked and an error message is shown if the name is either empty or * already exists for another configuration. Also slashes '/' are filtered out, as they can not * be used for names. * </p> * * @param parent */ private void addNameControl(Composite parent) { Label label = new Label(parent, SWT.NONE); label.setText("Configuration name"); final Text name = new Text(parent, SWT.BORDER); name.setText(fConfig.getName()); name.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1)); // Add a modify Listener to update the configuration for any name // changes name.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { // Upon a modify event check that the name is not empty and // is not already taken by another configuration. // Either case will generate an error message which will // disable the OK button. String newname = name.getText(); fConfig.setName(newname); if (newname.length() == 0) { Status status = new Status(Status.ERROR, "AVRDude", "Configuration name may not be empty", null); AVRDudeConfigEditor.this.updateStatus(status); } else if (fAllConfigs.contains(newname)) { Status status = new Status(Status.ERROR, "AVRDude", "Configuration with the same name already exists", null); AVRDudeConfigEditor.this.updateStatus(status); } else { AVRDudeConfigEditor.this.updateStatus(Status.OK_STATUS); } } }); // Add a Verify Listener to suppress slashes ('/') name.addVerifyListener(new VerifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.VerifyListener#verifyText(org.eclipse.swt.events.VerifyEvent) */ public void verifyText(VerifyEvent event) { String text = event.text; if (text.indexOf('/') != -1) { event.doit = false; } } }); } /** * Adds the configuration description control. * <p> * This control edits the <code>ProgrammerConfig<code> description property. * </p> * * @param parent */ private void addDescriptionControl(Composite parent) { Label label = new Label(parent, SWT.NONE); label.setText("Description"); final Text description = new Text(parent, SWT.BORDER); description.setText(fConfig.getDescription()); description.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1)); description.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { String newdescription = description.getText(); fConfig.setDescription(newdescription); } }); } /** * Adds the Programmers selection controls. * <p> * This composite edits the <code>ProgrammerConfig<code> programmer property. * </p> * <p> * It consists of a List with all available programmers and a Textbox showing the definition of * the selected programmer from the avrdude configuration file These two controls are arranged * in a SashForm and wrapped in a Group. * </p> * * @param parent */ private void addProgrammersComposite(Composite parent) { Group listgroup = new Group(parent, SWT.SHADOW_ETCHED_IN); listgroup.setText("Programmer Hardware (-c)"); listgroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1)); FillLayout fl = new FillLayout(); fl.marginHeight = 5; fl.marginWidth = 5; listgroup.setLayout(fl); SashForm sashform = new SashForm(listgroup, SWT.HORIZONTAL); sashform.setLayout(new GridLayout(2, false)); final List list = new List(sashform, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); list.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); String[] allprogrammers = getProgrammers(); list.setItems(allprogrammers); Composite devicedetails = new Composite(sashform, SWT.NONE); devicedetails.setLayout(new GridLayout()); devicedetails.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); final Text fromtext = new Text(devicedetails, SWT.NONE); fromtext.setEditable(false); fromtext.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); final Text details = new Text(devicedetails, SWT.MULTI | SWT.BORDER); details.setEditable(false); details.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); list.addSelectionListener(new SelectionAdapter() { /* * (non-Javadoc) * @seeorg.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events. * SelectionEvent) */ @Override public void widgetSelected(SelectionEvent e) { String devicename = list.getItem(list.getSelectionIndex()); IProgrammer type = fConfigNameMap.get(devicename); fConfig.setProgrammer(type.getId()); updateDetails(type, fromtext, details); updateCommandPreview(); } }); String programmer = fConfig.getProgrammer(); IProgrammer type = fConfigIDMap.get(programmer); if (programmer.length() != 0) { list.select(list.indexOf(type.getDescription())); updateDetails(type, fromtext, details); } sashform.pack(); } /** * Add the configuration port control. * * @param parent */ private void addPortControl(Composite parent) { Label label = new Label(parent, SWT.NONE); label.setText("Override default port (-P)"); final Text port = new Text(parent, SWT.BORDER); port.setText(fConfig.getPort()); port.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1)); port.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { String newport = port.getText(); fConfig.setPort(newport); updateCommandPreview(); } }); } /** * Add the configuration baudrate control. * <p> * This is implemented as a Combo with all standard RS232 baudrates. * </p> * * @param parent */ private void addBaudrateControl(Composite parent) { Label label = new Label(parent, SWT.NONE); label.setText("Override default baudrate (-b)"); final Combo baudrate = new Combo(parent, SWT.BORDER | SWT.RIGHT); baudrate.setLayoutData(new GridData(SWT.LEFT, SWT.NONE, true, false, 2, 1)); baudrate.setItems(new String[] { "", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200", "230400", "460800" }); baudrate.select(baudrate.indexOf(fConfig.getBaudrate())); baudrate.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { String newbaudrte = baudrate.getText(); fConfig.setBaudrate(newbaudrte); updateCommandPreview(); } }); // Add a Verify Listener to suppress all non digits baudrate.addVerifyListener(new VerifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.VerifyListener#verifyText(org.eclipse.swt.events.VerifyEvent) */ public void verifyText(VerifyEvent event) { String text = event.text; if (!text.matches("[0-9]*")) { event.doit = false; } } }); } /** * Add the other options controls. * * <p> * This contains a single text field where any other options not covered by * the UI can be specified * </p> * * @param parent */ private void addOtherOptionsComposite(Composite parent) { Group group = new Group(parent, SWT.NONE); group.setText("Other options"); group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1)); group.setLayout(new GridLayout(1, false)); Label label = new Label(group, SWT.NONE); label.setText("Use this field to add any avdude option not covered by the plugin."); final Text options = new Text(group, SWT.BORDER); options.setText(fConfig.getOtherOptions()); options.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); options.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse. * swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { String otheroptions = options.getText(); fConfig.setOtherOptions(otheroptions); updateCommandPreview(); } }); } /** * Adds the Exitspec controls. * <p> * This contains two groups of radio buttons, one for the Reset line options, one for the Vcc * line options. * </p> * <p> * These two groups are wrapped in another Group. * </p> * * @param parent */ private void addExitspecComposite(Composite parent) { Group groupcontainer = new Group(parent, SWT.SHADOW_IN); groupcontainer.setText("State of Parallel Port lines after AVRDude exit"); groupcontainer.setForeground(parent.getForeground()); groupcontainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 3, 1)); FillLayout containerlayout = new FillLayout(SWT.HORIZONTAL); containerlayout.spacing = 10; containerlayout.marginWidth = 10; containerlayout.marginHeight = 5; groupcontainer.setLayout(containerlayout); FillLayout grouplayout = new FillLayout(SWT.VERTICAL); grouplayout.marginHeight = 5; grouplayout.marginWidth = 5; grouplayout.spacing = 5; // // The Reset line options // // The command line options are stored with setData() within the Radio // Buttons. // Group resetgroup = new Group(groupcontainer, SWT.NONE); resetgroup.setText("/Reset Line"); resetgroup.setLayout(grouplayout); Button resetDefault = new Button(resetgroup, SWT.RADIO); resetDefault.setText("restore to previous state"); resetDefault.setData(""); Button resetReset = new Button(resetgroup, SWT.RADIO); resetReset.setText("activated (-E reset)"); resetReset.setData("reset"); Button resetNoReset = new Button(resetgroup, SWT.RADIO); resetNoReset.setText("deactivated (-E noreset)"); resetNoReset.setData("noreset"); // Set according to the config String exitReset = fConfig.getExitspecResetline(); if ("noreset".equals(exitReset)) { resetNoReset.setSelection(true); } else if ("reset".equals(exitReset)) { resetReset.setSelection(true); } else { resetDefault.setSelection(true); } SelectionListener resetlistener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Button button = (Button) e.widget; fConfig.setExitspecResetline((String) button.getData()); updateCommandPreview(); } }; resetDefault.addSelectionListener(resetlistener); resetReset.addSelectionListener(resetlistener); resetNoReset.addSelectionListener(resetlistener); // // The Vcc Options group // // The command line options are stored with setData() within the Radio // Buttons. // Group vccgroup = new Group(groupcontainer, SWT.NONE); vccgroup.setText("Vcc Lines"); vccgroup.setLayout(grouplayout); Button vccDefault = new Button(vccgroup, SWT.RADIO); vccDefault.setText("restore to previous state"); vccDefault.setData(""); Button vccVCC = new Button(vccgroup, SWT.RADIO); vccVCC.setText("activated (-E vcc)"); vccVCC.setData("vcc"); Button vccNoVcc = new Button(vccgroup, SWT.RADIO); vccNoVcc.setText("deactivated (-E novcc)"); vccNoVcc.setData("novcc"); // Set according to the config String exitVcc = fConfig.getExitspecVCCline(); if ("novcc".equals(exitVcc)) { vccVCC.setSelection(true); } else if ("vcc".equals(exitVcc)) { vccNoVcc.setSelection(true); } else { vccDefault.setSelection(true); } SelectionListener vcclistener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Button button = (Button) e.widget; fConfig.setExitspecVCCline((String) button.getData()); updateCommandPreview(); } }; vccDefault.addSelectionListener(vcclistener); vccVCC.addSelectionListener(vcclistener); vccNoVcc.addSelectionListener(vcclistener); } /** * Add the post AVRDude invocation delay control. * * @param parent */ private void addPostAVRDudeDelayControl(Composite parent) { Label label = new Label(parent, SWT.NONE); label.setText("Delay between avrdude invocations"); final Text delay = new Text(parent, SWT.BORDER | SWT.RIGHT); GridData gd = new GridData(SWT.FILL, SWT.NONE, false, false, 1, 1); gd.widthHint = 60; delay.setLayoutData(gd); delay.setTextLimit(8); delay.setText(fConfig.getPostAvrdudeDelay()); delay.addModifyListener(new ModifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent) */ public void modifyText(ModifyEvent e) { String newdelay = delay.getText(); fConfig.setPostAvrdudeDelay(newdelay); } }); // Add a Verify Listener to suppress all non digits delay.addVerifyListener(new VerifyListener() { /* * (non-Javadoc) * @see * org.eclipse.swt.events.VerifyListener#verifyText(org.eclipse.swt.events.VerifyEvent) */ public void verifyText(VerifyEvent event) { String text = event.text; if (!text.matches("[0-9]*")) { event.doit = false; } } }); label = new Label(parent, SWT.NONE); label.setText("milliseconds"); } /** * Add a Preview Control. * <p> * The preview control shows the current avrdude options commandline. The content is set in the * {@link #updateCommandPreview()} method. * </p> * * @param parent */ private void addCommandlinePreview(Composite parent) { Label label = new Label(parent, SWT.NONE); label.setText("Command line preview"); fPreviewText = new Text(parent, SWT.BORDER); fPreviewText.setEditable(false); fPreviewText.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 2, 1)); } /** * Get the results from this dialog. * <p> * It will return a ProgrammerConfig with the updated items. * </p> * <p> * This should only be called when <code>open()</code> returned <code>OK</code> (OK Button * clicked). Otherwise canceled changes will be returned. * </p> * * @return The ProgrammerConfig with the modified values. */ public ProgrammerConfig getResult() { return fConfig; } /** * Gets a list of all Programmer names. * * @return an Array of <code>String</code> with the names of all known Programmers, sorted * alphabetically */ private String[] getProgrammers() { Set<String> nameset = fConfigNameMap.keySet(); String[] allnames = nameset.toArray(new String[nameset.size()]); Arrays.sort(allnames, String.CASE_INSENSITIVE_ORDER); return allnames; } /** * Update the Preview Text to show the current configuration as an avrdude options commandline. */ private void updateCommandPreview() { java.util.List<String> arglist = fConfig.getArguments(); // make a String of all arguments StringBuffer sb = new StringBuffer("avrdude "); for (String argument : arglist) { sb.append(argument).append(" "); } sb.append(" [...part specific options...]"); fPreviewText.setText(sb.toString()); } /** * Update the Programmers detail area * * @param entry * The <code>ConfigEntry</code> for the selected programmer * @param from * The <code>Text</code> control for the filename * @param details * The multiline <code>Text</code> control for the details */ private void updateDetails(IProgrammer type, Text from, Text details) { ConfigProgrammerEntry entry; try { entry = AVRDude.getDefault().getProgrammerInfo(type.getId()); } catch (AVRDudeException e) { // TODO Auto-generated catch block e.printStackTrace(); from.setText("Error reading avrdude.conf file"); return; } from.setText("Programmer details from [" + entry.fConfigfile.toOSString() + ":" + entry.fLinenumber + "]"); Job job = new UpdateDetailsJob(entry, details); job.setSystem(true); job.setPriority(Job.SHORT); job.schedule(); } /** * Internal Job to update the programmer detail area. * <p> * This is done as a Job, because calls to {@link AVRDude#getConfigDetailInfo(ConfigEntry)} can * take some time to load the avrdude configuration file (currently at 429KByte). * </p> * <p> * The job is instantiated with the ConfigEntry for which to display the details and the * multiline Text control into which to print the data. * </p> * * @see AVRDude#getConfigDetailInfo(ConfigEntry) * */ private static class UpdateDetailsJob extends Job { private final ConfigEntry fConfigEntry; private final Text fTextControl; /** * @param entry * The <code>ConfigEntry</code> for which to display the details * @param textcontrol * The multiline <code>Text</code> control for the details */ public UpdateDetailsJob(ConfigEntry entry, Text textcontrol) { super("Loading programmer details"); fConfigEntry = entry; fTextControl = textcontrol; } /* * (non-Javadoc) * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) */ @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask("Retrieving programmer info", 1); if (fTextControl.isDisposed()) { return Status.CANCEL_STATUS; } // Get the preformatted info String from the AVRDude class and // update the details Text control in the UI thread. final String content = AVRDude.getDefault().getConfigDetailInfo(fConfigEntry); Display display = fTextControl.getDisplay(); if (display != null && !display.isDisposed()) { display.syncExec(new Runnable() { public void run() { fTextControl.setText(content); } }); } monitor.worked(1); } catch (IOException ioe) { // If avrdude is working at all, the configuration // file should be readable as well. So there should // be no IOExceptions. // But just in case we log the Error Status status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID, "Can't access avrdude configuration file " + fConfigEntry.fConfigfile.toOSString(), ioe); AVRPlugin.getDefault().log(status); } finally { monitor.done(); } return Status.OK_STATUS; } } }