blob: aa9fd5f79d62312eb02aac62cc82d07b88f045b6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2010 Wind River Systems, Inc. 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:
* Ted R Williams (Wind River Systems, Inc.) - initial implementation
*******************************************************************************/
package org.eclipse.cdt.debug.ui.memory.memorybrowser;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.cdt.debug.core.model.provisional.IRecurringDebugContext;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
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.Control;
import org.eclipse.ui.PlatformUI;
public class GoToAddressBarWidget {
/**
* Character sequence that is unlikely to appear naturally in a recurring
* debug context ID or memory space ID
*/
private static String SEPARATOR = "<seperator>";
/**
* At a minimum, the expression history is kept on a per launch
* configuration basis. Where debug contexts (processes, in practice) can
* provide a recurring ID, we further divide the history by those IDs. This
* constant is used when no recurring context ID is available.
*/
private static String UNKNOWN_CONTEXT_ID = "Unknown";
private Combo fExpression;
private ControlDecoration fEmptyExpression;
private ControlDecoration fWrongExpression;
private Button fOKButton;
private Button fOKNewTabButton;
private Composite fComposite;
protected static int ID_GO_NEW_TAB = 2000;
private IStatus fExpressionStatus = Status.OK_STATUS;
/**
* @param parent
* @return
*/
public Control createControl(Composite parent)
{
fComposite = new Composite(parent, SWT.NONE);
PlatformUI.getWorkbench().getHelpSystem().setHelp(fComposite, // FIXME
".GoToAddressComposite_context"); //$NON-NLS-1$
GridLayout layout = new GridLayout();
layout.numColumns = 6;
layout.makeColumnsEqualWidth = false;
layout.marginHeight = 0;
layout.marginLeft = 0;
fComposite.setLayout(layout);
fExpression = createExpressionField(fComposite);
fOKButton = new Button(fComposite, SWT.NONE);
fOKButton.setText(Messages.getString("GoToAddressBarWidget.Go")); //$NON-NLS-1$
fOKButton.setEnabled(false);
fOKNewTabButton = new Button(fComposite, SWT.NONE);
fOKNewTabButton.setText(Messages.getString("GoToAddressBarWidget.NewTab")); //$NON-NLS-1$
fOKNewTabButton.setEnabled(false);
return fComposite;
}
/** The launch configuration attribute prefix used to persist expression history */
private final static String SAVED_EXPRESSIONS = "saved_expressions"; //$NON-NLS-1$
private final static int MAX_SAVED_EXPRESSIONS = 15 ;
private void addExpressionToHistoryPersistence( Object context, String expr, String memorySpace ) {
/*
* Get the saved expressions if any.
*
* They are in the form
*
* expression,expression,.....,expression
*/
ILaunch launch = getLaunch(context);
if(launch == null)
{
return;
}
String contextID = getRecurringContextID(context);
ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration();
String currentExpressions = "";
if (launchConfiguration != null) {
try {
ILaunchConfigurationWorkingCopy wc = launchConfiguration.getWorkingCopy();
if (wc != null) {
currentExpressions = wc.getAttribute(getSaveExpressionKey(contextID,memorySpace), "");
StringTokenizer st = new StringTokenizer(currentExpressions, ","); //$NON-NLS-1$
/*
* Parse through the list creating an ordered array for display.
*/
ArrayList<String> list = new ArrayList<String>();
while(st.hasMoreElements())
{
String expression = (String) st.nextElement();
list.add(expression);
}
if(!list.contains(expr))
{
list.add(expr);
while(list.size() > MAX_SAVED_EXPRESSIONS)
{
list.remove(0);
}
currentExpressions = "";
for ( int idx =0 ; idx < list.size(); idx ++ ) {
if(idx > 0)
{
currentExpressions += ",";
}
currentExpressions += list.get(idx);
}
wc.setAttribute(getSaveExpressionKey(contextID,memorySpace), currentExpressions);
wc.doSave();
}
}
}
catch(CoreException e) {
}
}
}
/**
* Clear all expression history persisted in the launch configuration that
* created the given debug context
*
* @param context
* the debug context. In practice, this will always be a process
* context
*/
public void clearExpressionHistoryPersistence(Object context) {
if(context == null) {
return;
}
ILaunch launch = getLaunch(context);
if(launch == null) {
return;
}
// We maintain history for every process this launch configuration has
// launched. And where memory spaces are involved, each space has its
// own history. Here we just wipe out the persistence of all processes
// and memory spaces stored in the launch configuration that created the
// given processes.
ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration();
if (launchConfiguration != null) {
try {
ILaunchConfigurationWorkingCopy wc = launchConfiguration.getWorkingCopy();
if (wc != null) {
Map<?,?> attributes = wc.getAttributes();
Iterator<?> iterator = attributes.keySet().iterator();
while (iterator.hasNext()) {
String key = (String)iterator.next();
if (key.startsWith(SAVED_EXPRESSIONS)) {
wc.removeAttribute(key);
}
}
wc.doSave();
}
}
catch(CoreException e) {
// Some unexpected snag working with the launch configuration
MemoryBrowserPlugin.log(e);
}
}
}
/**
* Get the expression history persisted in the launch configuration for the
* given debug context and memory space (where applicable)
*
* @param context
* the debug context. In practice, this will always be a process
* context
* @param memorySpace
* memory space ID or null if not applicable
* @return a list of expressions, or empty collection if no history
* available (never null)
* @throws CoreException
* if there's a problem working with the launch configuration
*/
private String[] getSavedExpressions(Object context, String memorySpace) throws CoreException {
/*
* Get the saved expressions if any.
*
* They are in the form
*
* expression,expression,.....,expression
*/
ILaunch launch = getLaunch(context);
if(launch == null) {
return new String[0];
}
ILaunchConfiguration launchConfiguration = launch.getLaunchConfiguration();
String expressions = "";
if (launchConfiguration != null) {
expressions = launchConfiguration.getAttribute(getSaveExpressionKey(getRecurringContextID(context),memorySpace), "");
}
StringTokenizer st = new StringTokenizer(expressions, ","); //$NON-NLS-1$
/*
* Parse through the list creating an ordered array for display.
*/
ArrayList<String> list = new ArrayList<String>();
while(st.hasMoreElements()) {
list.add(st.nextToken());
}
return list.toArray(new String[list.size()]);
}
/**
* Populate the expression history combobox based on the history persisted
* in the launch configuration for the given context and memory space (where
* applicable)
*
* @param context
* the debug context. In practice, this will always be a process
* context
* @param memorySpace
* memory space ID; null if not applicable
*/
public void loadSavedExpressions(Object context, String memorySpace)
{
try {
// Rebuild the combobox entries, but make sure we don't clear the
// (expression) field. It's an input field; don't trample anything
// the user may have previously entered or is in the process of
// entering (see bugzilla 356346). removeAll() clears all the
// entries and the field. remove(beg, end) leaves the field in-tact
// as long as it's not asked to remove the entry the user selected
// to set the current field value. So, if the current expression
// corresponds to an entry, we purge all the entries but that one.
String[] expressions = getSavedExpressions(context, memorySpace);
String currentExpression = fExpression.getText();
if (currentExpression.length() > 0)
{
int index = fExpression.indexOf(currentExpression);
if(index > 0) {
fExpression.remove(0, index-1);
}
index = fExpression.indexOf(currentExpression);
if (fExpression.getItemCount() - index - 1 > 1) {
fExpression.remove(index+1, fExpression.getItemCount()-1);
}
}
else {
// No expression to trample. Use removeAll()
fExpression.removeAll();
}
for (String expression : expressions) {
if (fExpression.indexOf(expression) < 0) {
fExpression.add(expression);
}
}
} catch (CoreException e) {
// Unexpected snag dealing with launch configuration
MemoryBrowserPlugin.log(e);
}
}
public void addExpressionToHistory(Object context, String expr, String memorySpace) {
/*
* Make sure it does not already exist, we do not want to show duplicates.
*/
if ( fExpression.indexOf(expr) == -1 ) {
/*
* Cap the size of the list.
*/
while ( fExpression.getItemCount() >= MAX_SAVED_EXPRESSIONS ) {
fExpression.remove(0);
}
/*
* Add the new expression to the combobox
*/
fExpression.add(expr);
}
/*
* Add it to the persistense database.
*/
addExpressionToHistoryPersistence(context, expr, memorySpace);
}
/**
* Clears the history of expressions for the given debug context, both in
* the GUI and the persistence data
*
* @param context
* the debug context. In practice, this will always be a process
* context.
*/
public void clearExpressionHistory(Object context) {
/*
* Clear the combobox
*/
fExpression.removeAll();
fExpression.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
/*
* Clear the history persisted in the launch configuration
*/
clearExpressionHistoryPersistence(context);
/*
* Make sure the status image indicator shows OK.
*/
handleExpressionStatus(Status.OK_STATUS);
}
private Combo createExpressionField(Composite parent){
/*
* Create the dropdown box for the editable expressions.
*/
Combo combo = new Combo(parent, SWT.DROP_DOWN | SWT.BORDER);
combo.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
updateButtons();
}
});
fEmptyExpression = new ControlDecoration(combo, SWT.LEFT | SWT.CENTER);
fEmptyExpression.setDescriptionText(Messages.getString("GoToAddressBarWidget.EnterExpressionMessage")); //$NON-NLS-1$
FieldDecoration fieldDec = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_REQUIRED);
fEmptyExpression.setImage(fieldDec.getImage());
fWrongExpression = new ControlDecoration(combo, SWT.LEFT | SWT.TOP);
fieldDec = FieldDecorationRegistry.getDefault().getFieldDecoration(FieldDecorationRegistry.DEC_ERROR);
fWrongExpression.setImage(fieldDec.getImage());
fWrongExpression.hide();
// leave enough room for decorators
GridData data = new GridData(GridData.FILL_HORIZONTAL);
data.horizontalIndent = Math.max(fEmptyExpression.getImage().getBounds().width, fWrongExpression.getImage().getBounds().width);
combo.setLayoutData(data);
return combo;
}
protected void updateButtons() {
boolean empty = getExpressionText().length() == 0;
fOKNewTabButton.setEnabled(!empty);
fOKButton.setEnabled(!empty);
if (empty)
fEmptyExpression.show();
else
fEmptyExpression.hide();
if (fExpressionStatus.isOK())
fWrongExpression.hide();
else
fWrongExpression.show();
}
public int getHeight()
{
int height = fComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
return height;
}
public Button getButton(int id)
{
if (id == IDialogConstants.OK_ID)
return fOKButton;
if (id == ID_GO_NEW_TAB)
return fOKNewTabButton;
return null;
}
/**
* Get expression text
* @return
*/
public String getExpressionText()
{
return fExpression.getText().trim();
}
/**
* Update expression text from the widget
* @param text
*/
public void setExpressionText(String text)
{
fExpression.setText(text);
}
public Combo getExpressionWidget()
{
return fExpression;
}
/**
* decorate expression field according to the status
* @param message
*/
public void handleExpressionStatus(final IStatus message) {
if (message.isOK()) {
fWrongExpression.hide();
} else {
fWrongExpression.setDescriptionText(message.getMessage());
fWrongExpression.show();
}
fExpressionStatus = message;
}
/**
* Return the expression status
* @return expression status
*/
public IStatus getExpressionStatus()
{
return fExpressionStatus;
}
private ILaunch getLaunch(Object context)
{
IAdaptable adaptable = null;
ILaunch launch = null;
if(context instanceof IAdaptable)
{
adaptable = (IAdaptable) context;
launch = ((ILaunch) adaptable.getAdapter(ILaunch.class));
}
return launch;
}
/**
* Get the identifier for the given context if it is a recurring one. See
* {@link IRecurringDebugContext}
*
* @param context
* the debug context
* @return the ID or UNKNOWN_CONTEXT_ID if the context is non-recurring or
* can't provide us its ID
*/
private String getRecurringContextID(Object context)
{
String id = UNKNOWN_CONTEXT_ID;
if (context instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) context;
IRecurringDebugContext recurringDebugContext = (IRecurringDebugContext)adaptable.getAdapter(IRecurringDebugContext.class);
if (recurringDebugContext != null) {
try {
id = recurringDebugContext.getContextID();
}
catch(DebugException e) {
// If the context can't give us the ID, just treat it as a
// non-recurring context
}
}
}
return id;
}
/**
* Get a key that we can use to persist the expression history for the given
* debug context and memory space (where applicable). The key is used within
* the scope of a launch configuration.
*
* @param contextID
* a recurring debug context ID; see
* {@link IRecurringDebugContext}
* @param memorySpace
* a memory space identifier, or null if not applicable
* @return they key which will be used to persist the expression history
*/
private String getSaveExpressionKey(String contextID, String memorySpace) {
assert contextID.length() > 0;
String key = SAVED_EXPRESSIONS + SEPARATOR + contextID;
if (memorySpace != null && memorySpace.length() > 0) {
key += SEPARATOR + memorySpace;
}
return key;
}
}