/*******************************************************************************
* Copyright (c) 2010, 2016 Ericsson 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:
* Ericsson - initial API and implementation
* Intel Corporation - Added Reverse Debugging BTrace support
*******************************************************************************/
package org.eclipse.cdt.debug.internal.ui.commands;
import java.net.URL;
import java.util.Map;
import org.eclipse.cdt.debug.core.model.IChangeReverseMethodHandler;
import org.eclipse.cdt.debug.core.model.IChangeReverseMethodHandler.ReverseDebugMethod;
import org.eclipse.cdt.debug.core.model.IReverseToggleHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.IRequest;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.actions.DebugCommandHandler;
import org.eclipse.debug.ui.contexts.DebugContextEvent;
import org.eclipse.debug.ui.contexts.IDebugContextListener;
import org.eclipse.debug.ui.contexts.IDebugContextService;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.commands.IElementUpdater;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.handlers.RadioState;
import org.eclipse.ui.menus.UIElement;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.services.IEvaluationService;
import org.osgi.framework.Bundle;
/**
* Command handler to toggle reverse debugging mode
*
* @since 7.0
*/
public class ReverseToggleCommandHandler extends DebugCommandHandler implements IDebugContextListener, IElementUpdater {
private static final ImageDescriptor REVERSE_TOGGLE_DEFAULT_IMAGE = getImageDescriptor("icons/obj16/reverse_toggle.gif"); //$NON-NLS-1$
private static final ImageDescriptor REVERSE_TOGGLE_SOFTWARE_ON_IMAGE = getImageDescriptor("icons/obj16/reverse_toggle_sw_on.png"); //$NON-NLS-1$
private static final ImageDescriptor REVERSE_TOGGLE_SOFTWARE_OFF_IMAGE = getImageDescriptor("icons/obj16/reverse_toggle_sw_off.png"); //$NON-NLS-1$
private static final ImageDescriptor REVERSE_TOGGLE_HARDWARE_ON_IMAGE = getImageDescriptor("icons/obj16/reverse_toggle_hw_on.png"); //$NON-NLS-1$
private static final ImageDescriptor REVERSE_TOGGLE_HARDWARE_OFF_IMAGE = getImageDescriptor("icons/obj16/reverse_toggle_hw_off.png"); //$NON-NLS-1$
@Override
protected Class<?> getCommandType() {
return IReverseToggleHandler.class;
}
//
// The below logic allows us to keep the checked state of the toggle button
// properly set. This is because in some case, the checked state may change
// without the user actually pressing the button. For instance, if we restart
// the inferior, the toggle may automatically turn off.
// To figure this out, whenever a debug context changes, we make sure we are
// showing the proper checked state.
//
// We must hard-code the command id so as to know it from the very start (bug 290699)
private static final String REVERSE_TOGGLE_COMMAND_ID = "org.eclipse.cdt.debug.ui.command.reverseToggle"; //$NON-NLS-1$
private Object fActiveContext;
private IReverseToggleHandler fTargetAdapter;
private IDebugContextService fContextService;
private static ImageDescriptor getImageDescriptor (String path) {
Bundle bundle = Platform.getBundle("org.eclipse.cdt.debug.ui"); //$NON-NLS-1$
URL url = null;
if (bundle != null) {
url = FileLocator.find(bundle, new Path(path), null);
if (url != null) {
return ImageDescriptor.createFromURL(url);
}
}
return null;
}
public ReverseToggleCommandHandler() {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (window != null) {
fContextService = DebugUITools.getDebugContextManager().getContextService(window);
if (fContextService != null) {
fContextService.addPostDebugContextListener(this);
// This constructor might be called after the launch, so we must refresh here too.
// This can happen if we activate the action set after the launch.
refresh(fContextService.getActiveContext());
}
}
}
@Override
public void dispose() {
// Must use the stored context service. If we try to fetch the service
// again with the workbenchWindow, it may fail if the window is
// already closed.
if (fContextService != null) {
fContextService.removePostDebugContextListener(this);
}
fTargetAdapter = null;
fActiveContext = null;
super.dispose();
}
@Override
public void debugContextChanged(DebugContextEvent event) {
refresh(event.getContext());
}
private void refresh(ISelection selection) {
fTargetAdapter = null;
fActiveContext = null;
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection) selection;
if (!ss.isEmpty()) {
fActiveContext = ss.getFirstElement();
if (fActiveContext instanceof IAdaptable) {
fTargetAdapter = getAdapter((IAdaptable) fActiveContext);
}
}
}
ICommandService commandService = PlatformUI.getWorkbench().getService(ICommandService.class);
if (commandService != null) {
commandService.refreshElements(REVERSE_TOGGLE_COMMAND_ID, null);
}
}
private IReverseToggleHandler getAdapter(IAdaptable adaptable) {
IReverseToggleHandler adapter = adaptable.getAdapter(IReverseToggleHandler.class);
if (adapter == null) {
IAdapterManager adapterManager = Platform.getAdapterManager();
if (adapterManager.hasAdapter(adaptable, getCommandType().getName())) {
adapter = (IReverseToggleHandler)adapterManager.loadAdapter(adaptable, IReverseToggleHandler.class.getName());
}
}
if (adapter instanceof IChangeReverseMethodHandler) {
return adapter;
} else {
return null;
}
}
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
ReverseDebugMethod newMethod;
try {
if (HandlerUtil.matchesRadioState(event)) {
return null;
}
String radioState = event.getParameter(RadioState.PARAMETER_ID);
if (radioState.equals("UseSoftTrace")) { //$NON-NLS-1$
newMethod = ReverseDebugMethod.SOFTWARE;
} else if (radioState.equals("TraceOff")) { //$NON-NLS-1$
newMethod = ReverseDebugMethod.OFF;
} else if (radioState.equals("UseHardTrace")) { //$NON-NLS-1$
newMethod = ReverseDebugMethod.HARDWARE;
} else {
// undefined trace method
throw new ExecutionException(Messages.ReverseDebugging_UndefinedTraceMethod);
}
// store the parameter in the gdb command handler class
if (fTargetAdapter != null && fTargetAdapter instanceof IChangeReverseMethodHandler) {
ReverseDebugMethod currMethod = ((IChangeReverseMethodHandler)fTargetAdapter).getReverseDebugMethod(fActiveContext);
if (currMethod == newMethod) {
return null;
}
((IChangeReverseMethodHandler)fTargetAdapter).setReverseDebugMethod(newMethod);
}
// execute the event
super.execute(event);
// and finally update the radio current state
HandlerUtil.updateRadioState(event.getCommand(), radioState);
return null;
} catch (NullPointerException | ExecutionException e) {
// Disable tracing
if (fTargetAdapter != null && fTargetAdapter instanceof IChangeReverseMethodHandler) {
if (fTargetAdapter.toggleNeedsUpdating()) {
ReverseDebugMethod currMethod = ((IChangeReverseMethodHandler)fTargetAdapter).getReverseDebugMethod(fActiveContext);
if (currMethod == ReverseDebugMethod.OFF) {
ReverseDebugMethod prevMethod = ((IChangeReverseMethodHandler)fTargetAdapter).getPreviousReverseDebugMethod(fActiveContext);
if (prevMethod == ReverseDebugMethod.HARDWARE) {
newMethod = ReverseDebugMethod.HARDWARE;
} else {
newMethod = ReverseDebugMethod.SOFTWARE;
}
} else {
newMethod = ReverseDebugMethod.OFF;
}
((IChangeReverseMethodHandler)fTargetAdapter).setReverseDebugMethod(newMethod);
}
}
super.execute(event);
return null;
}
}
/*
* (non-Javadoc)
* @see org.eclipse.debug.ui.actions.DebugCommandHandler#postExecute(org.eclipse.debug.core.IRequest, java.lang.Object[])
*
* We keep this logic for users that may not do the refresh themselves.
*/
@Override
protected void postExecute(final IRequest request, Object[] targets) {
super.postExecute(request, targets);
new WorkbenchJob("") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (request.getStatus() != null && request.getStatus().getCode() != 0) {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
Shell activeShell = null;
if (window != null) {
activeShell = window.getShell();
} else {
activeShell = new Shell(PlatformUI.getWorkbench().getDisplay());
}
MessageDialog dialogbox = new MessageDialog(activeShell, Messages.ReverseDebugging_Error,
null, request.getStatus().getMessage(), MessageDialog.ERROR,
new String[] {IDialogConstants.OK_LABEL}, 0);
dialogbox.open();
}
// Request re-evaluation of property "org.eclipse.cdt.debug.ui.isReverseDebuggingEnabled" to update
// visibility of reverse stepping commands.
IEvaluationService exprService = PlatformUI.getWorkbench().getService(IEvaluationService.class);
if (exprService != null) {
exprService.requestEvaluation("org.eclipse.cdt.debug.ui.isReverseDebuggingEnabled"); //$NON-NLS-1$
}
// Refresh reverse toggle commands with the new state of reverse enabled.
// This is in order to keep multiple toggle actions in UI in sync.
ICommandService commandService = PlatformUI.getWorkbench().getService(ICommandService.class);
if (commandService != null) {
commandService.refreshElements(REVERSE_TOGGLE_COMMAND_ID, null);
}
return Status.OK_STATUS;
}
}.schedule();
}
@Override
public void updateElement(UIElement element,
@SuppressWarnings("rawtypes") Map parameters) {
if (fTargetAdapter != null && fTargetAdapter instanceof IChangeReverseMethodHandler) {
ReverseDebugMethod currMethod = ((IChangeReverseMethodHandler)fTargetAdapter).getReverseDebugMethod(fActiveContext);
ICommandService commandService = PlatformUI.getWorkbench().getService(ICommandService.class);
try{
if (currMethod == ReverseDebugMethod.HARDWARE) {
HandlerUtil.updateRadioState(commandService.getCommand(REVERSE_TOGGLE_COMMAND_ID), "UseHardTrace"); //$NON-NLS-1$
element.setTooltip(Messages.ReverseDebugging_ToggleHardwareTrace);
element.setIcon(REVERSE_TOGGLE_HARDWARE_ON_IMAGE);
} else if (currMethod == ReverseDebugMethod.SOFTWARE) {
HandlerUtil.updateRadioState(commandService.getCommand(REVERSE_TOGGLE_COMMAND_ID), "UseSoftTrace"); //$NON-NLS-1$
element.setTooltip(Messages.ReverseDebugging_ToggleSoftwareTrace);
element.setIcon(REVERSE_TOGGLE_SOFTWARE_ON_IMAGE);
} else {
HandlerUtil.updateRadioState(commandService.getCommand(REVERSE_TOGGLE_COMMAND_ID), "TraceOff"); //$NON-NLS-1$
element.setTooltip(Messages.ReverseDebugging_ToggleReverseDebugging);
ReverseDebugMethod prevMethod = ((IChangeReverseMethodHandler)fTargetAdapter).getPreviousReverseDebugMethod(fActiveContext);
if (prevMethod == ReverseDebugMethod.HARDWARE) {
element.setIcon(REVERSE_TOGGLE_HARDWARE_OFF_IMAGE);
} else if (prevMethod == ReverseDebugMethod.SOFTWARE) {
element.setIcon(REVERSE_TOGGLE_SOFTWARE_OFF_IMAGE);
} else {
element.setIcon(REVERSE_TOGGLE_DEFAULT_IMAGE);
}
}
}
catch(ExecutionException e){
// Do nothing
}
}
}
}