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