/*******************************************************************************
* 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.wizards;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectNature;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
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.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;
import org.eclipse.ui.progress.UIJob;
import de.innot.avreclipse.PluginIDs;
import de.innot.avreclipse.core.avrdude.AVRDudeException;
import de.innot.avreclipse.core.avrdude.AVRDudeSchedulingRule;
import de.innot.avreclipse.core.avrdude.ProgrammerConfig;
import de.innot.avreclipse.core.properties.AVRProjectProperties;
import de.innot.avreclipse.core.properties.ProjectPropertyManager;
import de.innot.avreclipse.core.toolinfo.AVRDude;
import de.innot.avreclipse.core.toolinfo.fuses.ByteValues;
import de.innot.avreclipse.core.toolinfo.fuses.FuseType;
import de.innot.avreclipse.core.toolinfo.fuses.Fuses;
import de.innot.avreclipse.core.util.AVRMCUidConverter;
import de.innot.avreclipse.ui.dialogs.AVRDudeErrorDialogJob;
import de.innot.avreclipse.ui.dialogs.SelectProgrammerDialog;
/**
* The "New Fuses File" / "New Lockbits File" Wizard page.
* <p>
* This is the UI part of the "Fuses File" Wizard. It is used for both FUSES and LOCKBITS. This is
* determined during instantiation by passing a {@link FuseType} to the constructor.
* </p>
* <p>
* On this page the following properties for the new fuses file can be edited:
* <ol>
* <li>The parent container for the new file</li>
* <li>The MCU type</li>
* <li>The name of the fuses file, either without an extension or with a ".fuses" / ".locks"
* extension</li>
* <li>The initial content of the file: Atmel defaults or loaded from an MCU.</li>
* </ol>
* </p>
* <p>
* The selected properties are accessible through the
* <ul>
* <li>{@link #getContainer()}</li>
* <li>{@link #getFileName()}</li>
* <li>{@link #getByteValues()}</li>
* </ul>
* methods.
* </p>
*
* @author Thomas Holland
* @since 2.3
*/
public class FusesWizardPage extends WizardPage {
// Texts for the Load from MCU Button
private static final String TEXT_LOADBUTTON = "Load from MCU";
private static final String TEXT_LOADBUTTON_BUSY = "Loading...";
// The Page title and Description
private final static String TITLE = "New AVR{0} file";
private final static String DESCRIPTION = "This wizard creates a new AVR {0} file.";
private Text fContainerText;
private Text fFileText;
private Combo fMCUCombo;
private Button fLoadButton;
private Button fDefaultsButton;
private Button fLoadedValuesButton;
private ByteValues fLoadedValues;
private final ISelection fSelection;
private final FuseType fType;
/**
* Constructor for FusesWizardPage.
*
* @param selection
* The resource selected when the Wizard was called. Will be used to determine and
* set the parent container for the new fuses file.
*/
public FusesWizardPage(ISelection selection, FuseType type) {
super("wizardPage");
String title = MessageFormat.format(TITLE, type.toString());
setTitle(title);
String description = MessageFormat.format(DESCRIPTION, type.toString());
setDescription(description);
fSelection = selection;
fType = type;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
*/
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
GridLayout layout = new GridLayout();
container.setLayout(layout);
layout.numColumns = 3;
layout.verticalSpacing = 9;
addParentFolderRow(container);
addMCUComboRow(container);
addFilenameRow(container);
addInitialContentRow(container);
initialize();
validateDialog();
setControl(parent);
}
private void addParentFolderRow(Composite parent) {
Label label = new Label(parent, SWT.NULL);
label.setText("&Folder:");
fContainerText = new Text(parent, SWT.BORDER | SWT.SINGLE);
fContainerText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
fContainerText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
validateDialog();
}
});
Button button = new Button(parent, SWT.PUSH);
button.setText("Browse...");
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
handleBrowse();
}
});
}
private void addFilenameRow(Composite parent) {
// Second Row: The File name
Label label = new Label(parent, SWT.NULL);
label.setText("&File name:");
fFileText = new Text(parent, SWT.BORDER | SWT.SINGLE);
fFileText.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 2, 1));
fFileText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
validateDialog();
}
});
}
private void addMCUComboRow(Composite parent) {
// Third Row: The MCU selection combo
Label label = new Label(parent, SWT.NULL);
label.setText("Target &MCU");
fMCUCombo = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
fMCUCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
fMCUCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
validateDialog();
// Check if the selected MCU is still valid for any loaded values
if (fLoadedValues != null && fLoadedValues.isCompatibleWith(getMCUId())) {
fLoadedValuesButton.setEnabled(true);
} else {
fLoadedValuesButton.setEnabled(false);
fDefaultsButton.setSelection(true);
}
}
});
// Load from Device Button
fLoadButton = new Button(parent, SWT.NONE);
fLoadButton.setText(TEXT_LOADBUTTON);
fLoadButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
fLoadButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
loadComboFromDevice();
}
});
}
private void addInitialContentRow(Composite parent) {
Group contentgroup = new Group(parent, SWT.NONE);
contentgroup.setText("Initial content");
contentgroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
contentgroup.setLayout(new GridLayout());
fDefaultsButton = new Button(contentgroup, SWT.RADIO);
fDefaultsButton.setText("Use default values (if available)");
fDefaultsButton
.setToolTipText("Use the default values defined in the Atmel part description files. Missing for some MCUs.");
fDefaultsButton.setSelection(true);
fLoadedValuesButton = new Button(contentgroup, SWT.RADIO);
fLoadedValuesButton.setText("Use the values loaded from the MCU");
fLoadedValuesButton.setEnabled(false);
}
/**
* Tests if the current workbench selection is a suitable container to use. Also determine the
* project of the selection and, if it is an AVR project, set the MCU and the filename according
* to the active target MCU.
*/
private void initialize() {
String mcuid = "atmega16"; // The default mcu
String filename = "new"; // The default filename
Object item = getSelectedItem(fSelection);
// Set the container value
IContainer container = null;
if (item instanceof IResource) {
if (item instanceof IContainer)
container = (IContainer) item;
else
container = ((IResource) item).getParent();
fContainerText.setText(container.getFullPath().toString());
}
IProject project = getProject(item);
// Get the MCU
if (project != null) {
filename = project.getName();
ProjectPropertyManager propsmanager = getProjectPropertiesManager(project);
if (propsmanager.isPerConfig()) {
// Get the name of the active configuration
// Get the active build configuration
IManagedBuildInfo bi = ManagedBuildManager.getBuildInfo(project);
IConfiguration activecfg = bi.getDefaultConfiguration();
filename = filename + "_" + activecfg.getName();
}
}
// Check if the file already exists. If yes, then we use an incremental counter
// to get a new filename
String fullname = filename + "." + fType.getExtension();
if (container != null) {
IFile file = container.getFile(new Path(fullname));
int i = 2;
while (file.exists()) {
fullname = filename + "_" + i++ + "." + fType.getExtension();
file = container.getFile(new Path(fullname));
}
}
// Set the filename
fFileText.setText(fullname);
// Build the list of MCUs with fuses for the MCU selection combo
// and select the active mcu
Set<String> allmcus = Fuses.getDefault().getMCUList();
List<String> mculist = new ArrayList<String>(allmcus);
Collections.sort(mculist);
for (String mcu : mculist) {
fMCUCombo.add(AVRMCUidConverter.id2name(mcu));
}
fMCUCombo.setVisibleItemCount(Math.min(mculist.size(), 20));
fMCUCombo.select(fMCUCombo.indexOf(AVRMCUidConverter.id2name(mcuid)));
}
/**
* Extract the first selection element of a structured selection.
*
* @param selection
* @return First element or <code>null</code> if the element could not be extracted.
*/
private Object getSelectedItem(ISelection selection) {
if (fSelection != null && fSelection.isEmpty() == false
&& fSelection instanceof IStructuredSelection) {
IStructuredSelection ssel = (IStructuredSelection) fSelection;
if (ssel.size() == 1)
return ssel.getFirstElement();
}
return null;
}
/**
* Extract a project from a selection item.
*
* @param item
* Either a <code>IProject</code> or an <code>IAdaptable</code>
* @return An <code>IProject</code> or <code>null</code> if the selection does not contain a
* project.
*/
private IProject getProject(Object item) {
IProject project = null;
// See if the given is an IProject (directly or via IAdaptable
if (item instanceof IProject) {
project = (IProject) item;
} else if (item instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) item;
project = (IProject) adaptable.getAdapter(IProject.class);
}
return project;
}
/**
* Get the {@link ProjectPropertyManager} for the given project.
* <p>
* This is a convenience method that adds some error checking to the
* <code>ProjectPropertyManager.getPropertyManager(project)</code> call at its core.
* </p>
*
* @param project
* An AVR project
* @return The <code>ProjectPropertyManager</code> for the given project, or <code>null</code>
* if no ProjectPropertyManager exists.
*/
private ProjectPropertyManager getProjectPropertiesManager(IProject project) {
try {
IProjectNature nature = project.getNature(PluginIDs.NATURE_ID);
if (nature != null) {
// This is an AVR Project
// Get the AVR properties for the active build
// configuration
return ProjectPropertyManager.getPropertyManager(project);
}
} catch (CoreException e) {
// Ignore the exception and continue with the default MCU;
}
return null;
}
/**
* Uses the standard container selection dialog to choose the new value for the parent folder
* field.
*/
private void handleBrowse() {
ContainerSelectionDialog dialog = new ContainerSelectionDialog(getShell(), ResourcesPlugin
.getWorkspace().getRoot(), false, "Select parent folder");
if (dialog.open() == ContainerSelectionDialog.OK) {
Object[] result = dialog.getResult();
if (result.length == 1) {
fContainerText.setText(((Path) result[0]).toString());
}
}
}
/**
* Validate all changes in the dialog.
*
*/
private void validateDialog() {
IContainer container = (IContainer) ResourcesPlugin.getWorkspace().getRoot().findMember(
new Path(getContainerName()));
String fileName = getFileName();
if (getContainerName().length() == 0) {
updateStatus("Folder must be specified");
return;
}
if (container == null
|| (container.getType() & (IResource.PROJECT | IResource.FOLDER)) == 0) {
updateStatus("Folder must exist");
return;
}
if (!container.isAccessible()) {
updateStatus("Project must be writable");
return;
}
if (fileName.length() == 0) {
updateStatus("File name must be specified");
return;
}
if (fileName.replace('\\', '/').indexOf('/', 1) > 0) {
updateStatus("File name must be valid");
return;
}
if (!fileName.endsWith("." + fType.getExtension())) {
String message = MessageFormat.format("File extension must be \"{0}\"", fType
.getExtension());
updateStatus(message);
return;
}
IFile file = container.getFile(new Path(fileName));
if (file.exists()) {
updateStatus("File already exists");
return;
}
updateStatus(null);
}
/**
* Update the dialog page status.
*
* @param message
* Error message to display or <code>null</code> if the dialog does not have any
* errors.
*/
private void updateStatus(String message) {
setErrorMessage(message);
setPageComplete(message == null);
}
/**
* Get the selected parent folder name.
*
* @return String with parent folder name
*/
public String getContainerName() {
return fContainerText.getText();
}
/**
* Get the selected filename.
*
* @return String with filename
*/
public String getFileName() {
return fFileText.getText();
}
/**
* Get the mcu id value of the selected MCU.
*
* @return String with mcu id
*/
private String getMCUId() {
String mcuname = fMCUCombo.getItem(fMCUCombo.getSelectionIndex());
String mcuid = AVRMCUidConverter.name2id(mcuname);
return mcuid;
}
/**
* Get a new <code>ByteValues</code> object set up to the user selection.
* <p>
* The object has the correct MCU and either - depending on user choice - the default byte
* values or the values loaded from an attached MCU.
*
* @return New <code>ByteValues</code> object.
*/
public ByteValues getNewByteValues() {
String mcuid = getMCUId();
if (fLoadedValues != null && fLoadedValues.isCompatibleWith(mcuid)) {
fLoadedValues.setMCUId(mcuid, false);
return fLoadedValues;
}
// Create new ByteValues object with the default values
ByteValues newvalues = new ByteValues(fType, mcuid);
newvalues.setDefaultValues();
return newvalues;
}
/**
* Load the actual MCU from the currently selected Programmer and set the MCU combo accordingly.
* <p>
* This method will start a new Job to load the values and return immediately.
* </p>
*/
private void loadComboFromDevice() {
ProgrammerConfig tmpcfg = getProgrammerConfig(fSelection);
if (tmpcfg == null) {
SelectProgrammerDialog dialog = new SelectProgrammerDialog(this.getShell(), null);
if (dialog.open() != IDialogConstants.OK_ID) {
return;
}
tmpcfg = dialog.getResult();
if (tmpcfg == null) {
return;
}
}
final ProgrammerConfig progcfg = tmpcfg;
// Disable the Load Button. It is re-enabled by the load job when the job finishes.
fLoadButton.setEnabled(false);
fLoadButton.setText(TEXT_LOADBUTTON_BUSY);
// The Job that does the actual loading.
Job readJob = new Job("Reading MCU Signature") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
monitor.beginTask("Starting AVRDude", 100);
// Get the current Values from the attached MCU
AVRDude avrdude = AVRDude.getDefault();
switch (fType) {
case FUSE:
fLoadedValues = avrdude.getFuseBytes(progcfg, new SubProgressMonitor(
monitor, 95));
break;
case LOCKBITS:
fLoadedValues = avrdude.getLockbits(progcfg, new SubProgressMonitor(
monitor, 95));
break;
}
if (fLoadedValues == null) {
return Status.CANCEL_STATUS;
}
// and update the user interface:
// Select the MCU in the combo and enable the "use loaded values" button.
if (!fLoadButton.isDisposed()) {
fLoadButton.getDisplay().syncExec(new Runnable() {
public void run() {
String mcuid = fLoadedValues.getMCUId();
fMCUCombo.select(fMCUCombo
.indexOf(AVRMCUidConverter.id2name(mcuid)));
fLoadedValuesButton.setEnabled(true);
}
});
}
monitor.worked(5);
} catch (AVRDudeException ade) {
// Show an Error message and exit
if (!fLoadButton.isDisposed()) {
UIJob messagejob = new AVRDudeErrorDialogJob(fLoadButton.getDisplay(), ade,
progcfg.getId());
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 (!fLoadButton.isDisposed()) {
fLoadButton.getDisplay().syncExec(new Runnable() {
public void run() {
// Re-Enable the Button
fLoadButton.setEnabled(true);
fLoadButton.setText(TEXT_LOADBUTTON);
}
});
}
}
return Status.OK_STATUS;
}
};
// now set the Job properties and start it
readJob.setRule(new AVRDudeSchedulingRule(progcfg));
readJob.setPriority(Job.SHORT);
readJob.setUser(true);
readJob.schedule();
}
/**
* Get the {@link ProgrammerConfig} for a selection.
* <p>
* This methods will try to get a project from the selection and, if it is an AVR project, get
* the active programmer.
* </p>
*
* @param selection
* A selection containing an <code>IProject</code>.
* @return Active <code>ProgrammerConfig</code> or <code>null</code> if no ProgrammerConfig
* could be retrieved from the selection.
*/
private ProgrammerConfig getProgrammerConfig(ISelection selection) {
Object item = getSelectedItem(selection);
if (item == null)
return null;
IProject project = getProject(item);
if (project == null)
return null;
ProjectPropertyManager propmanager = getProjectPropertiesManager(project);
if (propmanager == null)
return null;
AVRProjectProperties props = propmanager.getActiveProperties();
ProgrammerConfig progcfg = props.getAVRDudeProperties().getProgrammer();
return progcfg;
}
}