/*******************************************************************************
* 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.debug.ui;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.IBinary;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.debug.core.CDebugCorePlugin;
import org.eclipse.cdt.debug.core.CDebugUtils;
import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.cdt.debug.core.ICDebugConfiguration;
import org.eclipse.cdt.ui.CElementLabelProvider;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugModelPresentation;
import org.eclipse.debug.ui.ILaunchShortcut;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import org.eclipse.ui.dialogs.TwoPaneElementSelector;
import de.innot.avreclipse.debug.core.IAVRGDBConstants;
/**
* Launch Shortcut to Debug AVR Hardware
* <p>
* This Shortcut will get a list of all applicable binaries for the given <code>ISelection</code>/
* <code>IEditorPart</code> and launch a debugging session.
* </p>
* If a previous lauch configuration already exists for the binary it is used, otherwise a new
* <code>ILaunchConfiguration</code> is created and saved.</p>
* <p>
* The class will prompt the user to select:
* <ul>
* <li>if more than one binary is applicable</li>
* <li>If more than one launch configuration is applicable</li>
* </ul>
* If no binaries can be found for the given input then an error message is shown.
* </p>
*
* @author Thomas Holland
* @since 2.4
*
*/
public class LaunchShortcutDebugHardware implements ILaunchShortcut {
/*
* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchShortcut#launch(org.eclipse.jface.viewers.ISelection,
* java.lang.String)
*/
public void launch(ISelection selection, String mode) {
if (!isModeApplicable(mode)) {
// Should not happen => Bug in our Plugin
IStatus status = new Status(IStatus.ERROR, AVRGDBUIPlugin.PLUGIN_ID,
"LaunchShortcutDebugHardware: Wrong mode '" + mode + "'", null);
AVRGDBUIPlugin.log(status);
return;
}
if (selection instanceof IStructuredSelection) {
IBinary binary = searchBinary(((IStructuredSelection) selection).toArray(), mode);
launch(binary, mode);
}
}
/*
* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchShortcut#launch(org.eclipse.ui.IEditorPart,
* java.lang.String)
*/
public void launch(IEditorPart editor, String mode) {
if (!isModeApplicable(mode)) {
// Should not happen => Bug in our Plugin
IStatus status = new Status(IStatus.ERROR, AVRGDBUIPlugin.PLUGIN_ID,
"LaunchShortcutDebugHardware: Wrong mode '" + mode + "'", null);
AVRGDBUIPlugin.log(status);
return;
}
IBinary binary = searchBinary(new Object[] { editor.getEditorInput() }, mode);
launch(binary, mode);
}
/**
* Internal launch method.
* <p>
* It takes the binary, gets an existing launch configuration for this binary (or creates a new
* launch configuration) and hands this launch configuration over to the CDT Debug plugin for
* launching the debugger.
* </p>
*
* @param binary
* The binary file to launch
* @param mode
* The mode to launch in. Currently only "debug" is supported by this class
*/
protected void launch(IBinary binary, String mode) {
if (binary == null) {
return;
}
ILaunchConfiguration config = findLaunchConfiguration(binary, mode);
if (config != null) {
DebugUITools.launch(config, mode);
}
}
/**
* Searches the given IResource and/or IProject elements for any executable binary file.
* <p>
* The method will collect a list of all applicable binaries. To determine if a binary is
* applicable the {@link LaunchShortcutDebugHardware#isBinaryApplicable(IBinary)} method is
* called for each discovered binary.
* </p>
* <p>
* If more than one applicable binary is found, then the user is asked to choose one via a
* dialog.
* </p>
*
* @param elements
* Array of IResource, IProject or IBinary elements.
* @param mode
* @return One <code>IBinary</code>, or <code>null</code> if no applicable binaries could be
* found or if the user has canceled the selection dialog.
*/
protected IBinary searchBinary(final Object[] elements, String mode) {
// Sanity check. If nothing was selected pop an error message
if (elements == null || elements.length == 0) {
Shell shell = AVRGDBUIPlugin.getActiveShell();
String title = "Debug launcher";
String message = "Launch failed - no project or binary selected";
MessageDialog.openError(shell, title, message);
}
// Check if a single binary (*.elf) has been selected
// This is a quick exit if the user has selected an *.elf file.
if (elements.length == 1 && elements[0] instanceof IBinary) {
IBinary binary = (IBinary) elements[0];
if (isBinaryApplicable(binary)) {
return binary;
} else {
return null;
}
}
// The elements are something else.
// Go through all elements. If they are IResources then get their CProject.
// Then get all binaries for the project and filter them.
// All valid binaries are collected.
List<IBinary> allbins = new ArrayList<IBinary>();
for (Object element : elements) {
if (element instanceof IAdaptable) {
IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class);
if (res == null) {
continue;
}
ICProject cproject = CoreModel.getDefault().create(res.getProject());
if (cproject == null) {
continue;
}
try {
IBinary[] projectbins = cproject.getBinaryContainer().getBinaries();
for (IBinary binary : projectbins) {
if (isBinaryApplicable(binary)) {
allbins.add(binary);
}
}
} catch (CModelException e) {
// project can't deliver binaries - ignore
}
}
}
// No check how many binaries we have found
// 0: pop error message
// 1: return the single binary
// >1: Ask the user to select one
switch (allbins.size()) {
case 0:
Shell shell = AVRGDBUIPlugin.getActiveShell();
String title = "Debug launcher";
String message = "Launch failed - no binary found";
MessageDialog.openError(shell, title, message);
return null;
case 1:
return allbins.get(0);
default:
return selectBinary(allbins, mode);
}
}
/**
* Locate a configuration to relaunch for the given type. If one cannot be found, create one.
* <p>
* If more than one launch configurations already exists for the given binary, then the user is
* prompted to choose one with a dialog.
* </p>
*
* @return a re-usable config or <code>null</code> if no launch configuration could be created
* or when the user has canceled the selection dialog.
*/
protected ILaunchConfiguration findLaunchConfiguration(IBinary bin, String mode) {
// ILaunchConfigurationType is ID_LAUNCH_C_APP, an C application
ILaunchConfigurationType configType = getCLaunchConfigType();
List<ILaunchConfiguration> matchingConfigs = new ArrayList<ILaunchConfiguration>();
try {
IPath binaryname = bin.getResource().getProjectRelativePath();
// Get a list of all C type LaunchConfigurations.
// Then collect all configs that have the right path and project
ILaunchConfiguration[] configs = DebugPlugin.getDefault().getLaunchManager()
.getLaunchConfigurations(configType);
for (ILaunchConfiguration config : configs) {
IPath programPath = CDebugUtils.getProgramPath(config);
String projectName = CDebugUtils.getProjectName(config);
if (programPath != null && programPath.equals(binaryname)) {
if (projectName != null
&& projectName.equals(bin.getCProject().getProject().getName())) {
matchingConfigs.add(config);
}
}
}
} catch (CoreException ce) {
AVRGDBUIPlugin.log(ce.getStatus());
}
// No check how many LaunchConfigurations we have found
// 0: create a new one
// 1: return the single config
// >1: Ask the user to select one
switch (matchingConfigs.size()) {
case 0:
return createNewConfiguration(bin, mode);
case 1:
return matchingConfigs.get(0);
default:
return selectConfiguration(bin, matchingConfigs, mode);
}
}
/**
* Create a new launch configuration for the given binary.
* <p>
* The new launch configuration will have only the minimum number of attributes set. Everything
* else is, especially the AVR specific attributes, are not set so that their default value will
* be used by the debugger.
* </p>
*
* @param binary
* the <code>IBinary</code> for which the new launch configuration is created
* @param mode
* Currently "debug" is the only supported mode.
* @return the new launch configuration
*/
protected ILaunchConfiguration createNewConfiguration(IBinary binary, String mode) {
ILaunchConfiguration config = null;
try {
String projectName = binary.getResource().getProjectRelativePath().toString();
ILaunchConfigurationType configType = getCLaunchConfigType();
// Instantiate a new LaunchConfiguration
ILaunchConfigurationWorkingCopy wc = configType.newInstance(null, getLaunchManager()
.generateLaunchConfigurationName(binary.getElementName()));
// Set the project parameters
wc.setAttribute(ICDTLaunchConfigurationConstants.ATTR_PROGRAM_NAME, projectName);
wc.setAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, binary
.getCProject().getElementName());
wc.setMappedResources(new IResource[] { binary.getResource(),
binary.getResource().getProject() });
wc.setAttribute(ICDTLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY, (String) null);
// Get the active build configuration id and store it as attribute
ICProjectDescription projDes = CCorePlugin.getDefault().getProjectDescription(
binary.getCProject().getProject());
if (projDes != null) {
String buildConfigID = projDes.getActiveConfiguration().getId();
wc.setAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_BUILD_CONFIG_ID,
buildConfigID);
}
// Set the parameters for the startup tab
// TODO: see if we can replace them with default values
wc.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_STOP_AT_MAIN, true);
wc.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_START_MODE,
ICDTLaunchConfigurationConstants.DEBUGGER_MODE_RUN);
// We use the AVRGDBDebugger for the debugger configuration.
ICDebugConfiguration debugconfig = CDebugCorePlugin.getDefault().getDebugConfiguration(
IAVRGDBConstants.DEBUGGER_ID);
wc.setAttribute(ICDTLaunchConfigurationConstants.ATTR_DEBUGGER_ID, debugconfig.getID());
// And now save the config so it can be reused and/or edited.
config = wc.doSave();
} catch (CoreException ce) {
// Unlikely, but we need to inform the user that something did not work as expected.
AVRGDBUIPlugin.log(ce.getStatus());
MessageDialog.openError(AVRGDBUIPlugin.getActiveShell(), "Error", ce.getStatus()
.getMessage());
return null;
}
return config;
}
/**
* Prompts the user to select a configuration.
*
* @param bin
* The <code>IBinary</code> for which to select a Launch Configuration. Only used to
* display the name in the dialog message.
* @param configs
* List of all applicable launch configurations.
* @param mode
* the launch configuration mode. Only used for the message dialog.
* @return the selected binary or <code>null</code> if none (= cancel).
*/
protected ILaunchConfiguration selectConfiguration(IBinary bin,
List<ILaunchConfiguration> configs, String mode) {
IDebugModelPresentation labelProvider = DebugUITools.newDebugModelPresentation();
ElementListSelectionDialog dialog = new ElementListSelectionDialog(AVRGDBUIPlugin
.getActiveShell(), labelProvider);
dialog.setElements(configs.toArray());
dialog.setTitle("Launch Configuration Selection");
String text = "Multiple {0} configurations exist for the binary file '{1}'.\n\n"
+ "Please select the configuration to use";
String message = MessageFormat.format(text, mode, bin.getPath().lastSegment().toString());
dialog.setMessage(message);
dialog.setMultipleSelection(false);
int result = dialog.open();
labelProvider.dispose();
if (result == Window.OK) {
return (ILaunchConfiguration) dialog.getFirstResult();
}
return null;
}
/**
* Prompts the user to select a binary
*
* @param binList
* list of all matching <code>IBinary</code> elements.
* @param mode
* the launch configuration mode. Only used for the message dialog.
* @return the selected binary or <code>null</code> if none (= cancel).
*/
protected IBinary selectBinary(List<IBinary> binList, String mode) {
ILabelProvider programLabelProvider = new CElementLabelProvider() {
@Override
public String getText(Object element) {
if (element instanceof IBinary) {
IBinary bin = (IBinary) element;
StringBuilder name = new StringBuilder();
name.append(bin.getPath().lastSegment());
return name.toString();
}
return super.getText(element);
}
};
ILabelProvider contextLabelProvider = new CElementLabelProvider() {
@Override
public String getText(Object element) {
if (element instanceof IBinary) {
IBinary bin = (IBinary) element;
StringBuilder name = new StringBuilder();
// This will basically show the name of the project and configuration.
// I am not sure if this is the best way to display this.
// Better would be just the configuration - but what happens if the user selects
// multiple project for this LaunchConfigurationShortcut.
name.append(bin.getPath().removeLastSegments(0).toString());
return name.toString();
}
return super.getText(element);
}
};
TwoPaneElementSelector dialog = new TwoPaneElementSelector(AVRGDBUIPlugin.getActiveShell(),
programLabelProvider, contextLabelProvider);
dialog.setElements(binList.toArray());
dialog.setTitle("AVR Application");
String text = "Choose an AVR application binary to {0}";
String message = MessageFormat.format(text, mode);
dialog.setMessage(message);
dialog.setUpperListLabel("Binaries:");
dialog.setLowerListLabel("Context:");
dialog.setMultipleSelection(false);
if (dialog.open() == Window.OK) {
return (IBinary) dialog.getFirstResult();
}
return null;
}
/**
* Test if the a binary is applicable for this LaunchShortcut.
* <p>
* This method is used to filter all found binaries of the project(s) down to those that are
* applicable for this LaunchConfiguration. It will test if the given binary is
* <ul>
* <li>an executable</li>
* <li>has debugging info</li>
* <li>contains avr code</li>
* </ul>
* </p>
* <p>
* Subclasses can override to implement a different filter.
* </p>
*
* @param binary
* The <code>IBinary</code> to test.
* @return <code>true</code> if the binary matches.
*/
protected boolean isBinaryApplicable(IBinary binary) {
boolean result = binary.isExecutable() && "avr".equalsIgnoreCase(binary.getCPU())
&& binary.hasDebug();
return result;
}
/**
* Checks if the given mode is applicable for this launch shortcut.
* <p>
* Default implementation is <code>true</code> for '<code>debug</code>' and <code>false</code>
* for all other modes.
* </p>
* <p>
* Subclasses can override.
* </p>
*
* @param mode
* The
* @return
*/
protected boolean isModeApplicable(String mode) {
return ILaunchManager.DEBUG_MODE.equalsIgnoreCase(mode);
}
/**
* Method getCLaunchConfigType.
*
* @return ILaunchConfigurationType
*/
private ILaunchConfigurationType getCLaunchConfigType() {
return getLaunchManager().getLaunchConfigurationType(IAVRGDBConstants.LAUNCH_TYPE_ID);
}
/**
* Convenience method to get the Eclipse debug manager.
*
* @return The Eclipse debug manager class.
*/
private ILaunchManager getLaunchManager() {
return DebugPlugin.getDefault().getLaunchManager();
}
}