/** * Copyright (c) 2006 Mike Morearty * * Published under the MIT License: * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package expressionTracker.views; import java.util.ArrayList; import java.util.Iterator; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IDebugEventSetListener; import org.eclipse.debug.core.IExpressionManager; import org.eclipse.debug.core.model.IDebugElement; import org.eclipse.debug.core.model.IValue; import org.eclipse.debug.core.model.IWatchExpression; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableLayout; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.ui.IActionBars; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import expressionTracker.ExpressionTrackerPlugin; /** * An Eclipse view which shows expressions. Unlike the built-in "Expressions" view, * this view only evaluates each expression once, so you can use it to record earlier * values. * * @author mike@morearty.com */ public class ExpressionTrackerView extends ViewPart implements IDebugEventSetListener { private TableViewer viewer; private Display display; private Action addExpressionAction; private Action deleteExpressionAction; private Action doubleClickAction; private ArrayList items = new ArrayList(); // of ExpressionAndValue /** * A single line of our view: encapsulates an expression and its value. */ private class ExpressionAndValue { public IWatchExpression watchExpr; public String expression; public String value; /** * Constructor: given an expression in string form, begins the evaluation of * that expression. (The evaluation may complete on a separate thread.) */ public ExpressionAndValue(String expr) { // must add to 'item' before the below call to evaluate(), to avoid race // condition: handleDebugEvents() must be able to find this item items.add(this); // Save away the expression expression = expr; // Get the expression manager, and create a new watch expression IExpressionManager expressionManager = DebugPlugin.getDefault().getExpressionManager(); watchExpr = expressionManager.newWatchExpression(expr); // Figure out the "context" of this watch expression -- that is, which // stack frame is currently selected in Eclipse's "Debug" view? IDebugElement debugElement = null; IAdaptable debugContext = DebugUITools.getDebugContext(); if (debugContext != null) debugElement = (IDebugElement) debugContext.getAdapter(IDebugElement.class); if (debugElement == null) { value = ""; } else { // Have the watch expression begin evaluating itself. When it is // done, it will dispatch a DebugEvent, which we will see in our // handleDebugEvents() function. value = "..."; watchExpr.setExpressionContext(debugElement); watchExpr.evaluate(); } } /** * Called by handleDebugEvents() when this expression has been fully * evaluated. */ public void doneEvaluating() { try { if (watchExpr.hasErrors()) { String[] errorMessages = watchExpr.getErrorMessages(); if (errorMessages.length > 0) { // We should really display all the error messages, // not just the first... this.value = errorMessages[0]; } else { this.value = "Error"; } } else { IValue exprValue = watchExpr.getValue(); if (exprValue != null) this.value = exprValue.getValueString(); else this.value = ""; } } catch (DebugException e) { ExpressionTrackerPlugin.log("Error in Expression Tracker", e); } // We are probably not in the main UI thread; tell the main UI thread // to update the view at its convenience. display.asyncExec(new Runnable() { public void run() { viewer.update(ExpressionAndValue.this, null); } }); } } /** * The view asks its content provider for the actual data to be displayed. */ class ViewContentProvider implements IStructuredContentProvider { public void inputChanged(Viewer v, Object oldInput, Object newInput) { } public void dispose() { } public Object[] getElements(Object parent) { return items.toArray(); } } /** * The view calls its label provider to convert the data into labels and * icons. */ class ViewLabelProvider extends LabelProvider implements ITableLabelProvider { /** * Get the text for a given cell */ public String getColumnText(Object obj, int index) { ExpressionAndValue item = (ExpressionAndValue) obj; if (index == 0) { return item.expression; // column zero: the expression } else { return item.value; // column one: its value } } /** * Get the icon for a given cell */ public Image getColumnImage(Object obj, int index) { return getImage(obj); } public Image getImage(Object obj) { return PlatformUI.getWorkbench(). getSharedImages().getImage(ISharedImages.IMG_OBJ_ELEMENT); } } /** * The constructor. */ public ExpressionTrackerView() { // We register to be notified of all debug-related events, so that // we will be notified whenever an expression has been evaluated. DebugPlugin.getDefault().addDebugEventListener(this); } /** * This is a callback that will allow us * to create the viewer and initialize it. */ public void createPartControl(Composite parent) { viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); viewer.setContentProvider(new ViewContentProvider()); viewer.setLabelProvider(new ViewLabelProvider()); viewer.setInput(getViewSite()); viewer.setSorter(null); display = viewer.getTable().getShell().getDisplay(); Table table = viewer.getTable(); TableLayout layout = new TableLayout(); table.setLayout(layout); table.setHeaderVisible(true); // two columns: the expression, and its vlaue. TableColumn exprColumn = new TableColumn(viewer.getTable(), SWT.LEFT, 0); layout.addColumnData(new ColumnWeightData(30)); exprColumn.setText("Expression"); TableColumn valueColumn = new TableColumn(viewer.getTable(), SWT.LEFT, 1); layout.addColumnData(new ColumnWeightData(70)); valueColumn.setText("Value"); makeActions(); hookContextMenu(); hookDoubleClickAction(); contributeToActionBars(); } private void hookContextMenu() { MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { ExpressionTrackerView.this.fillContextMenu(manager); } }); Menu menu = menuMgr.createContextMenu(viewer.getControl()); viewer.getControl().setMenu(menu); getSite().registerContextMenu(menuMgr, viewer); } private void contributeToActionBars() { IActionBars bars = getViewSite().getActionBars(); fillLocalPullDown(bars.getMenuManager()); fillLocalToolBar(bars.getToolBarManager()); } private void fillLocalPullDown(IMenuManager manager) { manager.add(addExpressionAction); manager.add(new Separator()); manager.add(deleteExpressionAction); } private void fillContextMenu(IMenuManager manager) { manager.add(addExpressionAction); manager.add(deleteExpressionAction); // Other plug-ins can contribute there actions here manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); } private void fillLocalToolBar(IToolBarManager manager) { manager.add(addExpressionAction); manager.add(deleteExpressionAction); } private void makeActions() { addExpressionAction = new Action() { public void run() { // Prompt the user for a new expression InputDialog dlg = new InputDialog(viewer.getControl().getShell(), "Add Expression", "Expression:", "", new IInputValidator() { public String isValid(String newText) { if (newText.length() == 0) return ""; // disables the OK button else return null; // enables the OK button } }); if (dlg.open() == InputDialog.OK) { // Create the new expression based on the user's input ExpressionAndValue item = new ExpressionAndValue(dlg.getValue()); // show the new item and select it viewer.refresh(); viewer.setSelection(new StructuredSelection(item), true); } } }; addExpressionAction.setText("Add Expression"); addExpressionAction.setToolTipText("Adds an expression and evaluates it"); addExpressionAction.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages(). getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK)); deleteExpressionAction = new Action() { public void run() { // Iterate through all selected expressions, and delete them from 'items' IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); Iterator iterator = selection.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); items.remove(obj); } viewer.refresh(); } }; deleteExpressionAction.setText("Delete"); deleteExpressionAction.setToolTipText("Deletes the selected expression(s)"); deleteExpressionAction.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages(). getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK)); doubleClickAction = new Action() { public void run() { ISelection selection = viewer.getSelection(); Object obj = ((IStructuredSelection)selection).getFirstElement(); showMessage("Double-click detected on "+obj.toString()); } }; } private void hookDoubleClickAction() { viewer.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { doubleClickAction.run(); } }); } private void showMessage(String message) { MessageDialog.openInformation( viewer.getControl().getShell(), "Expression Tracker", message); } /** * Passing the focus request to the viewer's control. */ public void setFocus() { viewer.getControl().setFocus(); } /** * Our view is being destroyed. */ public void dispose() { // We need to stop listening for debug events. DebugPlugin.getDefault().removeDebugEventListener(this); // Let our superclass do anything it needs to do. super.dispose(); } /** * This is called by Eclipse whenever any debugging-related events takes place. * We use it to check for when an IWatchExpression has been evaluated. * When that happens, a DebugEvent is generated with getKind() == DebugEvent.CHANGE, * and getSource() equal to the IWatchExpression that was evaluated. */ public void handleDebugEvents(DebugEvent[] events) { for (int i=0; i