/******************************************************************************* * 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.text.MessageFormat; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.events.FocusAdapter; import org.eclipse.swt.events.FocusEvent; 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.graphics.FontMetrics; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; 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.Composite; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.console.MessageConsole; import org.eclipse.ui.progress.UIJob; import de.innot.avreclipse.AVRPlugin; import de.innot.avreclipse.core.avrdude.AVRDudeException; import de.innot.avreclipse.core.avrdude.AVRDudeSchedulingRule; import de.innot.avreclipse.core.avrdude.BaseBytesProperties; import de.innot.avreclipse.core.properties.AVRDudeProperties; import de.innot.avreclipse.core.toolinfo.fuses.ByteValues; import de.innot.avreclipse.core.toolinfo.fuses.ConversionResults; import de.innot.avreclipse.core.toolinfo.fuses.FuseType; import de.innot.avreclipse.core.util.AVRMCUidConverter; import de.innot.avreclipse.ui.AVRUIPlugin; import de.innot.avreclipse.ui.controls.FuseBytePreviewControl; import de.innot.avreclipse.ui.dialogs.AVRDudeErrorDialogJob; import de.innot.avreclipse.ui.dialogs.ByteValuesEditorDialog; import de.innot.avreclipse.ui.dialogs.ProjectMCUMismatchDialog; /** * The base AVRDude Tab page for Fuses and Lockbits. * <p> * The GUI for Fuse bytes and for Lockbits is the same and is handled in this class. The subclasses * just supply a few basic informations, but do not need to do any user interface handling. * </p> * <p> * Subclasses of this tab have three radio buttons: * <ul> * <li>Do not upload anything</li> * <li>Upload the byte values defined in a file</li> * <li>Upload some immediate byte values</li> * </ul> * Also a detailed preview of the selected bytes is shown. * </p> * * @author Thomas Holland * @since 2.2 * */ public abstract class AbstractTabAVRDudeBytes extends AbstractAVRDudePropertyTab { private final static int LABEL_GROUPNAME = 0; private final static int LABEL_NAME = 1; // The GUI texts private final static String GROUP_NAME = "Upload {0}"; private final static String TEXT_NOUPLOAD = "do not set {0}"; private final static String TEXT_FROMFILE = "from {0} file"; private final static String TEXT_IMMEDIATE = "direct hex value{0}"; private final static String WARN_BYTESINCOMPATIBLE = "These hex values are for an {0} MCU.\n" + "This is not compatible with the {2} MCU setting [{1}]."; private final static String WARN_BUTTON_CONVERT = "Convert"; private final static String WARN_FROMPROJECT = "project"; private final static String WARN_FROMCONFIG = "build configuration"; // Warning image private static final Image IMG_WARN = PlatformUI .getWorkbench() .getSharedImages() .getImage( ISharedImages.IMG_OBJS_WARN_TSK); // ToolTip texts for the hex value actions private final static String MENU_EDIT = "Start editor"; private final static String MENU_READDEVICE = "Load from MCU"; private final static String TEXT_LOADING = "Loading from MCU..."; private final static String MENU_COPYFILE = "Copy from file"; private final static String MENU_DEFAULTS = "Set to default (if available)"; private final static String MENU_ALLONES = "Set all bits to 1"; private final static String MENU_ALLZEROS = "Set all bits to 0"; private final static String MENU_CLEARALL = "Clear all bytes"; // The GUI widgets private Button fNoUploadButton; private Button fUploadFileButton; private Text fFileText; private Composite fFileWarningCompo; private Label fFileWarningLabel; private Button fWorkplaceButton; private Button fFilesystemButton; private Button fVariableButton; private Button fImmediateButton; private Composite fBytesCompo; private ToolBar fActionsToolBar; private Label fLoadingLabel; private Composite[] fByteCompos; private Text[] fValueTexts; private Label[] fFuseLabels; private Composite fHexWarningCompo; private Label fWarningLabel; private Button fConvertButton; private FuseBytePreviewControl fPreviewControl; /** List of all created Images to dispose them when this tab is disposed. */ private final List<Image> fImages = new ArrayList<Image>(ActionItem .values().length * 2); /** The Properties that this page works with */ private AVRDudeProperties fTargetProps; /** The BaseBytesProperties property object this page works with */ protected BaseBytesProperties fBytes; // The abstract hook methods for the subclasses /** * Get an array of label strings. * <p> * Currently the returned array must contain two Strings. The first entry is used for the group * label ("Upload {0}") and the second entry is used in multiple places like ("from {0} file"). * </p> * * @return Array of <code>String</code>s with label strings. */ protected abstract String[] getLabels(); /** * Get the type of fuse memory this tab is for, {@link FuseType#FUSE} or * {@link FuseType#LOCKBITS}. * * @return */ protected abstract FuseType getType(); /** * Load the ByteValues from the target MCU with avrdude. * * @param avrdudeprops * The current properties, including the ProgrammerConfig needed by avrdude. * @return A <code>ByteValues</code> object with the bytes read from the MCU. * @throws AVRDudeException * for any Exception thrown by avrdude */ protected abstract ByteValues getByteValues(AVRDudeProperties avrdudeprops, IProgressMonitor monitor) throws AVRDudeException; /** * Get the Label text for the n-th byte. * * @param index * 0-5 for fuses, 0 for lockbits * @return <code>String</code> with the name of the byte at the index. */ protected abstract String getByteEditorLabel(int index); /** * Get an array with file extensions. * <p> * This list is used by the "from FileSystem" file dialog to show only files with the * appropriate extension. * * @return Array of <code>String</code>s with file extensions like ".fuses". */ protected abstract String[] getFileExtensions(); /** * Get the actual Byte properties this tab works with. * * @param avrdudeprops * Source properties * @return <code>FuseBytesProperties</code> or <code>LockbitBytesProperties</code> object * extracted from the given <code>AVRDudeProperties</code> */ protected abstract BaseBytesProperties getByteProps(AVRDudeProperties avrdudeprops); // The GUI stuff /* * (non-Javadoc) * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#dispose() */ @Override public void dispose() { // remove all allocated images for (Image image : fImages) { image.dispose(); } super.dispose(); } /* * (non-Javadoc) * @see * org.eclipse.cdt.ui.newui.AbstractCPropertyTab#createControls(org.eclipse.swt.widgets.Composite * ) */ @Override public void createControls(Composite parent) { // init the arrays int maxbytes = getType().getMaxBytes(); fByteCompos = new Composite[maxbytes]; fValueTexts = new Text[maxbytes]; fFuseLabels = new Label[maxbytes]; parent.setLayout(new GridLayout(1, false)); // Add the source selection group addSourceSelectionGroup(parent); // Add the detailed byte values preview fPreviewControl = new FuseBytePreviewControl(parent, SWT.BORDER); fPreviewControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); } /** * Add the main selection group. * <p> * This group has three sections (with radio buttons): * <ul> * <li>Do not write bytes</li> * <li>Write bytes from a user selectable file</li> * <li>Write the bytes given</li> * </ul> * </p> * * @param parent * Parent <code>Composite</code> */ private void addSourceSelectionGroup(Composite parent) { // Group Setup Group group = new Group(parent, SWT.NONE); group.setText(MessageFormat.format(GROUP_NAME, getLabels()[LABEL_GROUPNAME])); group.setLayout(new GridLayout(4, false)); group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1)); addNoUploadSection(group); // addSeparator(group); addFromFileSection(group); // addSeparator(group); addImmediateSection(group); addWarningSection(group); } /** * The "No upload" Section. * * @param parent * Parent <code>Composite</code> */ private void addNoUploadSection(Composite parent) { fNoUploadButton = new Button(parent, SWT.RADIO); fNoUploadButton.setText(MessageFormat.format(TEXT_NOUPLOAD, getLabels()[LABEL_NAME])); fNoUploadButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false, 1, 1)); fNoUploadButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // Set the properties fBytes.setWrite(false); // and disable the other controls enableFileGroup(false); enableByteGroup(false); updateFields(); // If the warning was active it is now made invisible checkValid(); } }); // Dummy to fill up the next 3 columns of the gridlayout Label dummy = new Label(parent, SWT.NONE); dummy.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1)); } /** * The "Upload from file" Section. * <p> * Contains a Text control to enter a filename and three buttons to select the filename from the * workplace, the filesystem or from a build variable. * </p> * * @param parent * Parent <code>Composite</code> */ private void addFromFileSection(Composite parent) { fUploadFileButton = new Button(parent, SWT.RADIO); fUploadFileButton.setText(MessageFormat.format(TEXT_FROMFILE, getLabels()[LABEL_NAME])); fUploadFileButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1)); fUploadFileButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (fUploadFileButton.getSelection() == false) { // Button was deselected (another button has been selected // The other button will handle everything return; } fBytes.setWrite(true); fBytes.setUseFile(true); enableFileGroup(true); enableByteGroup(false); updateFields(); updateAVRDudePreview(fTargetProps); fPreviewControl.setByteValues(fBytes.getByteValues()); checkValid(); } }); fFileText = new Text(parent, SWT.BORDER); fFileText.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 3, 1)); fFileText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { String newpath = fFileText.getText(); fBytes.setFileName(newpath); updateFields(); checkValid(); } }); // The next line in the GUI consists of a Warning composite and three file dialog buttons, // all wrapped in one composite. Composite compo = new Composite(parent, SWT.NONE); compo.setBackgroundMode(SWT.INHERIT_FORCE); compo.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 4, 1)); compo.setLayout(new GridLayout(4, false)); // Warning composite fFileWarningCompo = new Composite(compo, SWT.NONE); fFileWarningCompo.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false, 1, 1)); fFileWarningCompo.setLayout(new GridLayout(2, false)); Label warnicon = new Label(fFileWarningCompo, SWT.LEFT); warnicon.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); warnicon.setImage(IMG_WARN); fFileWarningLabel = new Label(fFileWarningCompo, SWT.WRAP); fFileWarningLabel.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); fFileWarningLabel.setText(""); // The three buttons fWorkplaceButton = setupWorkplaceButton(compo, fFileText); fFilesystemButton = setupFilesystemButton(compo, fFileText, getFileExtensions()); fVariableButton = setupVariableButton(compo, fFileText); } /** * The "Upload from direct values" Section. * <p> * Contains controls to edit all bytes directly and two buttons to read the byte values from the * programmer and to copy the values from the file. * </p> * * @param parent * Parent <code>Composite</code> */ private void addImmediateSection(Composite parent) { // add the radio button fImmediateButton = new Button(parent, SWT.RADIO); fImmediateButton.setText(MessageFormat.format(TEXT_IMMEDIATE, getType().getMaxBytes() > 1 ? "s" : "")); GridData buttonGD = new GridData(SWT.BEGINNING, SWT.TOP, false, false); // This is somewhat arbitrarily and looks good on my setup. // Your mileage may vary. buttonGD.verticalIndent = 8; fImmediateButton.setLayoutData(buttonGD); fImmediateButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (fImmediateButton.getSelection() == false) { // Button was deselected (another button has been selected // The other button will handle everything return; } fBytes.setWrite(true); fBytes.setUseFile(false); enableFileGroup(false); enableByteGroup(true); updateFields(); // Check if the byte values are compatible and display a warning if required checkValid(); } }); // add the byte editor composites (wrapped in a composite) fBytesCompo = new Composite(parent, SWT.NONE); GridData bytesGD = new GridData(SWT.BEGINNING, SWT.TOP, false, false, 1, 1); // Make the size of the byte edit fields somewhat dependent on the font // size. I use 6 chars instead of the actual required 2, because 2 was // just to small. FontMetrics fm = getFontMetrics(parent); bytesGD.widthHint = Dialog.convertWidthInCharsToPixels(fm, 6) * getType().getMaxBytes(); fBytesCompo.setLayoutData(bytesGD); fBytesCompo.setLayout(new FillLayout(SWT.HORIZONTAL)); // Insert the byte editor compos for (int i = 0; i < getType().getMaxBytes(); i++) { makeByteEditComposite(fBytesCompo, i); } // Add the actions menu fActionsToolBar = createActionsToolbar(parent); GridData toolbarGD = new GridData(SWT.BEGINNING, SWT.TOP, false, false); fActionsToolBar.setLayoutData(toolbarGD); // and the loading label fLoadingLabel = new Label(parent, SWT.NONE); fLoadingLabel.setText(TEXT_LOADING); GridData loadingGD = new GridData(SWT.BEGINNING, SWT.TOP, true, false); fLoadingLabel.setLayoutData(loadingGD); fLoadingLabel.setVisible(false); // Adjust the Layout: try to get all components to line up on their baseline. // This is more or less a hack and probably only looks good on my system. // But I don't know SWT well enough to do this the right way. // Feel free to do something else! Point sizeButton = fImmediateButton.computeSize(SWT.DEFAULT, SWT.DEFAULT); Point sizeToolBar = fActionsToolBar.computeSize(SWT.DEFAULT, SWT.DEFAULT); Point sizeEditors = fByteCompos[0].getChildren()[0].computeSize(SWT.DEFAULT, SWT.DEFAULT); Point sizeLabel = fLoadingLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT); // Toolbar is the master. Align the tops of the other three components in this row according // to the height of the toolbar. int tbheight = sizeToolBar.y; buttonGD.verticalIndent = tbheight - sizeButton.y - 3; bytesGD.verticalIndent = tbheight - sizeEditors.y - 2; loadingGD.verticalIndent = tbheight - sizeLabel.y - 5; } /** * Add the warning section, which consists of a composite that can be set visible or hidden as * required. * * @param parent * Parent <code>Composite</code> */ private void addWarningSection(Composite parent) { // The Warning Composite fHexWarningCompo = new Composite(parent, SWT.NONE); fHexWarningCompo.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 4, 1)); GridLayout gl = new GridLayout(3, false); gl.marginHeight = 0; gl.marginWidth = 0; gl.verticalSpacing = 0; gl.horizontalSpacing = 0; fHexWarningCompo.setLayout(gl); Label warnicon = new Label(fHexWarningCompo, SWT.LEFT); warnicon.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); warnicon.setImage(IMG_WARN); fWarningLabel = new Label(fHexWarningCompo, SWT.LEFT | SWT.WRAP); fWarningLabel.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); fWarningLabel.setText(""); fConvertButton = new Button(fHexWarningCompo, SWT.PUSH); fConvertButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); fConvertButton.setText(WARN_BUTTON_CONVERT); fConvertButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // Convert the Fuse bytes to the MCU of the project / build configuration String mcuid = fTargetProps.getParent().getMCUId(); ByteValues newvalues = convertFusesTo(mcuid, fBytes.getByteValues()); fBytes.setByteValues(newvalues); checkValid(); updateFields(); } }); fHexWarningCompo.setVisible(false); } /** * Create a ToolBar with the actions for the direct Hex entry line. * <p> * The contents of the ToolBar are actually defined by the {@link ActionItem} enumeration. * </p> * * @param parent * <code>Composite</code> to which the ToolBar is added. * @return Reference to the ToolBar */ private ToolBar createActionsToolbar(Composite parent) { SelectionListener menuListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { ActionItem item = (ActionItem) e.widget.getData(); item.performAction(AbstractTabAVRDudeBytes.this); checkValid(); updateFields(); } }; ToolBar toolbar = new ToolBar(parent, SWT.FLAT); for (ActionItem item : ActionItem.values()) { ToolItem ti = new ToolItem(toolbar, SWT.PUSH); ti.setToolTipText(item.tooltipText); ti.setData(item); ti.addSelectionListener(menuListener); Image image = item.image.createImage(); Image disabledimage = item.disabledImage.createImage(); ti.setImage(image); ti.setDisabledImage(disabledimage); fImages.add(image); fImages.add(disabledimage); } return toolbar; } /** * Create an byte "Editor" composite. * <p> * The editor consists of a Text control to enter the byte value and a Label below it with the * name of the byte. * </p> * <p> * The Text control will only accept (up to) 2 hex digits (converted to uppercase). The value is * stored as an <code>int</code> in the target properties, with <code>-1</code> representing an * empty value. * </p> * <p> * The Editor uses a <code>FillLayout</code> to pack both elements tightly. It is up to the * caller to set the LayoutData for this composite * </p> * <p> * This method saves a reference to the Text control and the Label control in the * {@link #fValueTexts} respectively {@link #fFuseLabels} arrays for access to them outside of * this method. * </p> * * @param parent * Parent <code>Composite</code> * @param index * The byte index for the editor. * * @return <code>Composite</code> with the editor. */ private Composite makeByteEditComposite(Composite parent, int index) { // TODO: maybe more elegant if coded as a separate class extending composite. That way we // could also get rid of the three ugly arrays with references to the hex editor components. FillLayout layout = new FillLayout(SWT.VERTICAL); Composite compo = new Composite(parent, SWT.NONE); compo.setLayout(layout); fByteCompos[index] = compo; // Add the Text control Text text = new Text(compo, SWT.BORDER | SWT.CENTER); text.setTextLimit(2); text.setSize(10, 20); text.setData(Integer.valueOf(index)); // Add a modification listener to set the fuse byte text.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { // Get the byte number Text source = (Text) e.widget; int index = (Integer) source.getData(); // Get the new value... int newvalue; if (source.getText().length() > 0) { newvalue = Integer.parseInt(source.getText(), 16); } else { // Text control is empty. Use the default newvalue = -1; } // ... and set the property (if the source text control is enabled) // The check is necessary because this event handler is // also called when the #setText(String) method of this Text control // is called, even when the control is disabled. // The check prevents unnecessary updates of the previews. if (source.isEnabled()) { fBytes.setValue(index, newvalue); updateAVRDudePreview(fTargetProps); fPreviewControl.setByteValues(fBytes.getByteValues()); } } }); // Add a verify listener to only accept hex digits and convert them to // upper case text.addVerifyListener(new VerifyListener() { public void verifyText(VerifyEvent event) { String text = event.text.toUpperCase(); if (!text.matches("[0-9A-F]*")) { event.doit = false; } event.text = text; } }); // Add a focus listener to select the complete text when the control gets the focus text.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { Text source = (Text) e.widget; source.selectAll(); } }); // Store a reference to the Text control, so that the updateData() // method can update the content when required. fValueTexts[index] = text; // Add the label Label fuselabel = new Label(compo, SWT.CENTER); fuselabel.setText(Integer.toString(index)); fuselabel.setSize(10, 0); fFuseLabels[index] = fuselabel; return compo; } /** * Enable / Disable the file selector Controls * * @param enabled * <code>true</code> to enable, <code>false</code> to disable. */ private void enableFileGroup(boolean enabled) { fFileText.setEnabled(enabled); fWorkplaceButton.setEnabled(enabled); fFilesystemButton.setEnabled(enabled); fVariableButton.setEnabled(enabled); } /** * Enable / Disable the Byte Editor Controls * <p> * When enabling, only those editors are enabled that are actually valid for the current MCU. * </p> * * @param enabled * <code>true</code> to enable, <code>false</code> to disable. */ private void enableByteGroup(boolean enabled) { for (int i = 0; i < fByteCompos.length; i++) { setEnabled(fByteCompos[i], enabled); } fActionsToolBar.setEnabled(enabled); } /** * Check if the MCU from the active ByteValues is compatible with the MCU from the project. * <p> * Three possible results: * <ul> * <li>The MCUs are the same: do nothing</li> * <li>The MCUs are not the same but compatible: Silently convert the ByteValues to the new MCU</li> * <li>The MCUs are not compatible: Show the warning and the convert button</li> * </ul> * </p> */ private void checkValid() { // clear all warnings fFileWarningCompo.setVisible(false); fHexWarningCompo.setVisible(false); String projectmcuid = fTargetProps.getParent().getMCUId(); if (!fBytes.getWrite()) { // No write - no warning return; } if (fBytes.getUseFile()) { // Check if the file is valid. // If not the FileWarningCompo is activated ByteValues filebv = null; String message = null; try { filebv = fBytes.getByteValuesFromFile(); if (filebv == null) { message = "Could not access the file"; } else if (!filebv.isCompatibleWith(projectmcuid)) { String filemcu = AVRMCUidConverter.id2name(filebv.getMCUId()); String projectmcu = AVRMCUidConverter.id2name(projectmcuid); message = MessageFormat.format( "File is for an {0} MCU,\nincompatible with {2} MCU [{1}]", filemcu, projectmcu, isPerConfig() ? WARN_FROMCONFIG : WARN_FROMPROJECT); } } catch (CoreException ce) { IStatus status = ce.getStatus(); int code = status.getCode(); switch (code) { case BaseBytesProperties.FILE_EMPTY_FILENAME: message = "Empty filename"; break; case BaseBytesProperties.FILE_NOT_FOUND: message = "File not found"; break; case BaseBytesProperties.FILE_MCU_PROPERTY_MISSING: message = "File has no 'MCU=xxx' property"; break; case BaseBytesProperties.FILE_WRONG_TYPE: message = MessageFormat.format("File is not a {0} file", getType() .toString()); break; case BaseBytesProperties.FILE_INVALID_FILENAME: message = "Invalid filename"; break; default: message = "Internal error accessing the file."; } } if (message != null) { fFileWarningLabel.setText(message); fFileWarningCompo.pack(); fFileWarningCompo.setVisible(true); } return; } // Immediate values are used String ourmcuid = fBytes.getMCUId(); if (projectmcuid.equals(ourmcuid)) { // Identical MCUs - hide the warning and do nothing return; } if (fBytes.isCompatibleWith(projectmcuid)) { // Compatible - no warning // But convert the ByteValues anyway so everything is in sync. ByteValues newvalues = convertFusesTo(projectmcuid, fBytes.getByteValues()); fBytes.setByteValues(newvalues); updateFields(); return; } // The two MCUs are not compatible. // Update the warning composite with the MCU names and show it. String valuesmcu = AVRMCUidConverter.id2name(fBytes.getMCUId()); String projectmcu = AVRMCUidConverter.id2name(projectmcuid); String message = MessageFormat.format(WARN_BYTESINCOMPATIBLE, valuesmcu, projectmcu, isPerConfig() ? WARN_FROMCONFIG : WARN_FROMPROJECT); fWarningLabel.setText(message); fConvertButton.setVisible(true); fHexWarningCompo.pack(); fHexWarningCompo.setVisible(true); } /* * (non-Javadoc) * @see * de.innot.avreclipse.ui.propertypages.AbstractAVRPropertyTab#performApply(de.innot.avreclipse * .core.preferences.AVRProjectProperties) */ @Override protected void performApply(AVRDudeProperties dstprops) { if (fTargetProps == null) { // updataData() has not been called and this tab has no (modified) // settings yet. return; } // Copy the currently selected values of this tab to the given, fresh // Properties. // The caller of this method will handle the actual saving // Copy the settings from the FuseBytesProperties sub-properties BaseBytesProperties src = getByteProps(fTargetProps); BaseBytesProperties dst = getByteProps(dstprops); dst.setWrite(src.getWrite()); dst.setUseFile(src.getUseFile()); dst.setFileName(src.getFileName()); dst.setValues(src.getValuesFromImmediate()); } /* * (non-Javadoc) * @see * de.innot.avreclipse.ui.propertypages.AbstractAVRPropertyTab#performDefaults(de.innot.avreclipse * .core.preferences.AVRProjectProperties) */ @Override protected void performCopy(AVRDudeProperties srcprops) { // Copy the settings from the BaseBytesProperties sub-properties BaseBytesProperties src = getByteProps(srcprops); BaseBytesProperties dst = getByteProps(fTargetProps); dst.setWrite(src.getWrite()); dst.setUseFile(src.getUseFile()); dst.setFileName(src.getFileName()); dst.setValues(src.getValuesFromImmediate()); 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; fBytes = getByteProps(props); // Set the text for the filename fFileText.setText(fBytes.getFileName()); // Check if the values are valid and show a warning (if required) checkValid(); // Update the radio buttons // There are three possibilities: // a) No upload wanted: Write == false // b) Upload from file: Write == true && useFile == true // c) Upload from immediate: Write == true && useFile == false if (!fBytes.getWrite()) { // a) No upload wanted fNoUploadButton.setSelection(true); // fUploadFileButton.setSelection(false); fImmediateButton.setSelection(false); enableFileGroup(false); enableByteGroup(false); fPreviewControl.setByteValues(null); } else { // write bytes fNoUploadButton.setSelection(false); if (fBytes.getUseFile()) { // b) write bytes - use supplied file fUploadFileButton.setSelection(true); fImmediateButton.setSelection(false); enableFileGroup(true); enableByteGroup(false); } else { // c) write bytes - use immediate bytes fUploadFileButton.setSelection(false); fImmediateButton.setSelection(true); enableFileGroup(false); enableByteGroup(true); } } updateFields(); } /** * Update all fields showing fuse byte values. * <p> * These are: * <ul> * <li>The Fuse Byte Editor Text controls</li> * <li>The Fuse Byte Values Preview control</li> * <li>The AVRDude command line preview</li> * </ul> * This method should be called whenever any fuse byte value has changed. * </p> */ private void updateFields() { // Update the Fuse Byte Editor Texts. int[] values = fBytes.getValuesFromImmediate(); int count = getType().getMaxBytes(); for (int i = 0; i < count; i++) { if (i < values.length) { String newvalue = ""; int currvalue = values[i]; if (0 <= currvalue && currvalue <= 255) { newvalue = "00" + Integer.toHexString(currvalue).toUpperCase(); newvalue = newvalue.substring(newvalue.length() - 2); } fValueTexts[i].setText(newvalue); fFuseLabels[i].setText(getByteEditorLabel(i)); fByteCompos[i].setVisible(true); } else { // byte value index > than max. supported by the current Fuse MCU. // hide the editor compo fByteCompos[i].setVisible(false); fValueTexts[i].setText(""); } } // Update the Fuses Preview. // If the "no write" flag is set, the preview is cleared (set to null) if (fBytes.getWrite()) { fPreviewControl.setByteValues(fBytes.getByteValues()); } else { fPreviewControl.setByteValues(null); } // Update the AVRDUDE preview updateAVRDudePreview(fTargetProps); } /** * Convert a ByteValues object to a new MCU, color the fuse bytes preview according to the * conversion results and print the results to the console. * * @param targetmcu * The new MCU value * @param sourcevalues * The <code>ByteValues</code> to convert * @return A new <code>ByteValues</code> object valid for the targetmcu. */ private ByteValues convertFusesTo(final String targetmcu, final ByteValues sourcevalues) { sourcevalues.setMCUId(targetmcu, true); fPreviewControl.setByteValues(sourcevalues); ConversionResults results = sourcevalues.getConversionResults(); if (results != null) { MessageConsole console = AVRPlugin.getDefault().getConsole("Fuse Byte Conversion"); results.printToConsole(console); } return sourcevalues; } /** * Load the Bytes from the currently attached MCU. * <p> * This method will start a new Job to load the values and return immediately. * </p> */ private void readFuseBytesFromDevice() { // Disable the Actions Menu. It is re-enabled by the load job when it finishes. fActionsToolBar.setEnabled(false); fLoadingLabel.setVisible(true); // The Job that does the actual loading. Job readJob = new Job("Reading Fuse Bytes") { @Override protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask("Starting AVRDude", 100); final ByteValues bytevalues = getByteValues(fTargetProps, new SubProgressMonitor(monitor, 95)); // and update the user interface if (!fActionsToolBar.isDisposed()) { fActionsToolBar.getDisplay().syncExec(new Runnable() { public void run() { // Check if the mcus match String projectmcu = fTargetProps.getParent().getMCUId(); String newmcu = bytevalues.getMCUId(); if (!bytevalues.isCompatibleWith(projectmcu)) { // No, they don't match. Ask the user what to do // "Convert to project MCU", "Accept anyway" or "Cancel" Dialog dialog = new ProjectMCUMismatchDialog(fActionsToolBar .getShell(), newmcu, projectmcu, getType(), isPerConfig()); int choice = dialog.open(); switch (choice) { case ProjectMCUMismatchDialog.CANCEL: return; case ProjectMCUMismatchDialog.ACCEPT: // Accept the values including their MCUId. // Set the properties accordingly fBytes.setByteValues(bytevalues); break; case ProjectMCUMismatchDialog.CONVERT: // Convert the values to the current project MCU ByteValues newvalues = convertFusesTo(projectmcu, bytevalues); fBytes.setByteValues(newvalues); break; } } else { // MCUs are compatible. fBytes.setByteValues(bytevalues); } updateData(fTargetProps); } }); } monitor.worked(5); } catch (AVRDudeException ade) { // Show an Error message and exit if (!fActionsToolBar.isDisposed()) { UIJob messagejob = new AVRDudeErrorDialogJob(fActionsToolBar.getDisplay(), ade, fTargetProps.getProgrammerId()); messagejob.setPriority(Job.INTERACTIVE); messagejob.schedule(); try { messagejob.join(); // block until the dialog is closed. } catch (InterruptedException e) { // Don't care if the dialog is interrupted from outside. } } } catch (SWTException swte) { // The display has been disposed, so the user is not // interested in the results from this job return Status.CANCEL_STATUS; } finally { monitor.done(); // Enable the Load from MCU Button if (!fActionsToolBar.isDisposed()) { fActionsToolBar.getDisplay().syncExec(new Runnable() { public void run() { // Re-Enable the Button fActionsToolBar.setEnabled(true); fLoadingLabel.setVisible(false); } }); } } return Status.OK_STATUS; } }; // now set the Job properties and start it readJob.setRule(new AVRDudeSchedulingRule(fTargetProps.getProgrammer())); readJob.setPriority(Job.SHORT); readJob.setUser(true); readJob.schedule(); } // The Images for the Actions ToolBar private static final ImageDescriptor IMG_EN_EDIT = AVRUIPlugin .getImageDescriptor("icons/objs16/e_edit_fuses.png"); private static final ImageDescriptor IMG_DIS_EDIT = AVRUIPlugin .getImageDescriptor("icons/objs16/d_edit_fuses.png"); private static final ImageDescriptor IMG_EN_LOADFILE = AVRUIPlugin .getImageDescriptor("icons/objs16/e_copy_fusefile.png"); private static final ImageDescriptor IMG_DIS_LOADFILE = AVRUIPlugin .getImageDescriptor("icons/objs16/d_copy_fusefile.png"); private static final ImageDescriptor IMG_EN_READMCU = AVRUIPlugin .getImageDescriptor("icons/objs16/e_read_mcu.png"); private static final ImageDescriptor IMG_DIS_READMCU = AVRUIPlugin .getImageDescriptor("icons/objs16/d_read_mcu.png"); private static final ImageDescriptor IMG_EN_DEFAULT = AVRUIPlugin .getImageDescriptor("icons/objs16/e_copy_default.png"); private static final ImageDescriptor IMG_DIS_DEFAULT = AVRUIPlugin .getImageDescriptor("icons/objs16/d_copy_default.png"); private static final ImageDescriptor IMG_EN_ALLONES = AVRUIPlugin .getImageDescriptor("icons/objs16/e_0xff.png"); private static final ImageDescriptor IMG_DIS_ALLONES = AVRUIPlugin .getImageDescriptor("icons/objs16/d_0xff.png"); private static final ImageDescriptor IMG_EN_ALLZEROS = AVRUIPlugin .getImageDescriptor("icons/objs16/e_0x00.png"); private static final ImageDescriptor IMG_DIS_ALLZEROS = AVRUIPlugin .getImageDescriptor("icons/objs16/d_0x00.png"); private static final ImageDescriptor IMG_EN_CLEAR = AVRUIPlugin .getImageDescriptor("icons/objs16/e_clear_bytes.png"); private static final ImageDescriptor IMG_DIS_CLEAR = AVRUIPlugin .getImageDescriptor("icons/objs16/d_clear_bytes.png"); /** * Enumeration of all Actions for the hex editor actions Toolbar. */ private enum ActionItem { // Edit Action EDIT(MENU_EDIT, IMG_EN_EDIT, IMG_DIS_EDIT) { @Override public void performAction(AbstractTabAVRDudeBytes tab) { ByteValuesEditorDialog dialog = new ByteValuesEditorDialog(tab.fActionsToolBar .getShell(), tab.fBytes.getByteValues()); dialog.create(); // dialog.getShell().setSize(100, 100); dialog.optimizeSize(); int result = dialog.open(); if (result == Dialog.OK) { ByteValues newvalues = dialog.getResult(); tab.fBytes.setByteValues(newvalues); } } }, // Copy from file Action COPY(MENU_COPYFILE, IMG_EN_LOADFILE, IMG_DIS_LOADFILE) { @Override public void performAction(AbstractTabAVRDudeBytes tab) { tab.fBytes.syncFromFile(); } }, // Read from MCU Action READ(MENU_READDEVICE, IMG_EN_READMCU, IMG_DIS_READMCU) { @Override public void performAction(AbstractTabAVRDudeBytes tab) { tab.readFuseBytesFromDevice(); } }, // Set to default values action DEFAULTS(MENU_DEFAULTS, IMG_EN_DEFAULT, IMG_DIS_DEFAULT) { @Override public void performAction(AbstractTabAVRDudeBytes tab) { tab.fBytes.setDefaultValues(); } }, // Set all bytes to 0xff ALL_1(MENU_ALLONES, IMG_EN_ALLONES, IMG_DIS_ALLONES) { @Override public void performAction(AbstractTabAVRDudeBytes tab) { setBytes(tab.fBytes, 0xff); } }, // Set all bytes to 0x00 ALL_0(MENU_ALLZEROS, IMG_EN_ALLZEROS, IMG_DIS_ALLZEROS) { @Override public void performAction(AbstractTabAVRDudeBytes tab) { setBytes(tab.fBytes, 0x00); } }, // Set all bytes to -1 CLEAR(MENU_CLEARALL, IMG_EN_CLEAR, IMG_DIS_CLEAR) { @Override public void performAction(AbstractTabAVRDudeBytes tab) { tab.fBytes.clearValues(); } }; public final String tooltipText; public final ImageDescriptor image; public final ImageDescriptor disabledImage; private ActionItem(String text, ImageDescriptor image, ImageDescriptor disabledImage) { this.tooltipText = text; this.image = image; this.disabledImage = disabledImage; } /** * Perform the action associated with this item. * * @param tab * A reference to the parent object to get access to its internl values. */ public abstract void performAction(AbstractTabAVRDudeBytes tab); /** * Convenience method to set all Bytes of a <code>ByteValues</code> object to the same * value. * * @param bytevalues * <code>ByteValues</code> object * @param value * The new value for all bytes */ private static void setBytes(BaseBytesProperties bytevalues, int value) { int count = bytevalues.getValues().length; for (int i = 0; i < count; i++) { bytevalues.setValue(i, value); } } } }