de.innot.avreclipse.ui.propertypages.AbstractTabAVRDudeBytes.java Source code

Java tutorial

Introduction

Here is the source code for de.innot.avreclipse.ui.propertypages.AbstractTabAVRDudeBytes.java

Source

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

    }

}