/******************************************************************************* * Copyright (c) 2006, 2015 Wind River Systems 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.dsf.debug.ui.viewmodel.expression; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.RequestMonitor; import org.eclipse.cdt.dsf.debug.ui.viewmodel.IDebugVMConstants; import org.eclipse.cdt.dsf.ui.concurrent.ViewerCountingRequestMonitor; import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMContext; import org.eclipse.cdt.dsf.ui.viewmodel.AbstractVMNode; import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode; import org.eclipse.cdt.dsf.ui.viewmodel.VMDelta; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.IExpressionManager; import org.eclipse.debug.core.model.IExpression; import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementEditor; import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider; import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta; import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.debug.ui.IDebugUIConstants; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ICellModifier; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.TreePath; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.widgets.Composite; /** * This is the top-level view model node in the expressions view. Its job is to: * <li> * <ol> retrieve the {@link IExpression} objects from the global {@link IExpressionManager},</ol> * <ol> retrieve the expression string from the <code>IExpression</code> object,</ol> * <ol> then to call the configured expression nodes to parse the expression string.</ol> * </li> * <p> * This node is not intended to have any standard child nodes, therefore * the implementation of {@link #setChildNodes(IVMNode[])} throws an exception. * Instead users should call {@link #setExpressionNodes(IExpressionVMNode[])} * to configure the nodes that this node will delegate to when processing expressions. * </p> */ public class ExpressionManagerVMNode extends AbstractVMNode implements IElementLabelProvider, IElementEditor { /** * VMC for a new expression object to be added. When user clicks on this node to * edit it, he will create a new expression. */ public class NewExpressionVMC extends AbstractVMContext { public NewExpressionVMC() { super(ExpressionManagerVMNode.this); } @Override public <T> T getAdapter(Class<T> adapter) { return super.getAdapter(adapter); } @Override public boolean equals(Object obj) { return obj instanceof NewExpressionVMC; } @Override public int hashCode() { return getClass().hashCode(); } } /** Local reference to the global expression manager */ private IExpressionManager fManager = DebugPlugin.getDefault().getExpressionManager(); /** Cached reference to a cell modifier for editing expression strings of invalid expressions */ private ICellModifier fWatchExpressionCellModifier = null; /** * @since 2.1 * * @return The cell modifier to be used when editing. If you need to provide * a custom cell editor you would override this method. */ protected ICellModifier createCellModifier() { return new WatchExpressionCellModifier(); } public ExpressionManagerVMNode(ExpressionVMProvider provider) { super(provider); } @Override public String toString() { return "ExpressionManagerVMNode"; //$NON-NLS-1$ } private ExpressionVMProvider getExpressionVMProvider() { return (ExpressionVMProvider)getVMProvider(); } @Override public void update(IHasChildrenUpdate[] updates) { // Test availability of children based on whether there are any expressions // in the manager. We assume that the getExpressions() will just read // local state data, so we don't bother using a job to perform this // operation. for (int i = 0; i < updates.length; i++) { boolean hasChildren = fManager.getExpressions().length != 0; if (!hasChildren && updates[i].getPresentationContext().getColumns() != null) { hasChildren = true; } updates[i].setHasChilren(hasChildren); updates[i].done(); } } @Override public void update(IChildrenCountUpdate[] updates) { for (IChildrenCountUpdate update : updates) { if (!checkUpdate(update)) continue; // We assume that the getExpressions() will just read local state data, // so we don't bother using a job to perform this operation. int count = fManager.getExpressions().length; // Account for "Add New Expression" element if (update.getPresentationContext().getColumns() != null) { count += 1; } update.setChildCount(count); update.done(); } } @Override public void update(final IChildrenUpdate[] updates) { for (IChildrenUpdate update : updates) { doUpdateChildren(update); } } public void doUpdateChildren(final IChildrenUpdate update) { final IExpression[] expressions = fManager.getExpressions(); // For each (expression) element in update, find the layout node that can // parse it. And for each expression that has a corresponding layout node, // call IExpressionLayoutNode#getElementForExpression to generate a VMC. // Since the last is an async call, we need to create a multi-RM to wait // for all the calls to complete. final CountingRequestMonitor multiRm = new ViewerCountingRequestMonitor(getVMProvider().getExecutor(), update); int multiRmCount = 0; int lowOffset= update.getOffset(); if (lowOffset < 0) { lowOffset = 0; } int length= update.getLength(); if (length <= 0) { length = expressions.length; } final int highOffset= lowOffset + length; // If columns are present, add the "Add New Expression" element. int expressionsLength = expressions.length; if (update.getPresentationContext().getColumns() != null) { expressionsLength += 1; } for (int i = lowOffset; i < highOffset && i < expressionsLength; i++) { if (i < expressions.length) { multiRmCount++; final int childIndex = i; final IExpression expression = expressions[i]; // getElementForExpression() accepts a IElementsUpdate as an argument. // Construct an instance of VMElementsUpdate which will call a // the request monitor when it is finished. The request monitor // will in turn set the element in the update argument in this method. ((ExpressionVMProvider)getVMProvider()).update( new VMExpressionUpdate( update, expression, new DataRequestMonitor<Object>(getVMProvider().getExecutor(), multiRm) { @Override protected void handleSuccess() { update.setChild(getData(), childIndex); multiRm.done(); } @Override protected void handleError() { update.setChild(new InvalidExpressionVMContext(ExpressionManagerVMNode.this, expression), childIndex); multiRm.done(); } }) ); } else { // Last element in the list of expressions is the "add new expression" // dummy entry. update.setChild(new NewExpressionVMC(), i); } } // If no expressions were parsed, we're finished. // Set the count to the counting RM. multiRm.setDoneCount(multiRmCount); } @Override public void update(ILabelUpdate[] updates) { // The label update handler only handles labels for the invalid expression VMCs. // The expression layout nodes are responsible for supplying label providers // for their VMCs. for (ILabelUpdate update : updates) { if (update.getElement() instanceof NewExpressionVMC) { updateNewExpressionVMCLabel(update, (NewExpressionVMC) update.getElement()); } else { update.done(); } } } /** * Updates the label for the NewExpressionVMC. */ private void updateNewExpressionVMCLabel(ILabelUpdate update, NewExpressionVMC vmc) { String[] columnIds = update.getColumnIds() != null ? update.getColumnIds() : new String[0]; for (int i = 0; i < columnIds.length; i++) { // Bug 373468: show "Add New Expression" label in name column if // expression column is not shown. if ( IDebugVMConstants.COLUMN_ID__EXPRESSION.equals(columnIds[i]) || (IDebugVMConstants.COLUMN_ID__NAME.equals(columnIds[i]) && !Arrays.asList(columnIds).contains(IDebugVMConstants.COLUMN_ID__EXPRESSION)) ) { update.setLabel(MessagesForExpressionVM.ExpressionManagerLayoutNode__newExpression_label, i); update.setImageDescriptor(DebugUITools.getImageDescriptor(IDebugUIConstants.IMG_LCL_ADD), i); FontData fontData = JFaceResources.getFontDescriptor(IDebugUIConstants.PREF_VARIABLE_TEXT_FONT).getFontData()[0]; // Bugzilla 287598: Distinguish 'Add new expression' entry from actual expressions fontData.setStyle(SWT.ITALIC); update.setFontData(fontData, i); } else { update.setLabel("", i); //$NON-NLS-1$ } } update.done(); } @Override public int getDeltaFlags(Object event) { int retVal = 0; // Add a flag if the list of expressions in the global expression manager has changed. if (event instanceof ExpressionsChangedEvent) { retVal |= IModelDelta.ADDED | IModelDelta.REMOVED | IModelDelta.INSERTED | IModelDelta.CONTENT ; } if ( event instanceof PropertyChangeEvent && IPresentationContext.PROPERTY_COLUMNS.equals(((PropertyChangeEvent)event).getProperty()) ) { retVal |= IModelDelta.CONTENT; } for (IExpression expression : fManager.getExpressions()) { retVal |= getExpressionVMProvider().getDeltaFlagsForExpression(expression, event); } return retVal; } @Override public void buildDelta(final Object event, final VMDelta parentDelta, final int nodeOffset, final RequestMonitor requestMonitor) { if (event instanceof ExpressionsChangedEvent) { buildDeltaForExpressionsChangedEvent((ExpressionsChangedEvent)event, parentDelta, nodeOffset, requestMonitor); } else if ( event instanceof PropertyChangeEvent && IPresentationContext.PROPERTY_COLUMNS.equals(((PropertyChangeEvent)event).getProperty()) ) { parentDelta.setFlags(parentDelta.getFlags() | IModelDelta.CONTENT); requestMonitor.done(); } else { // For each expression, find its corresponding node and ask that // layout node for its delta flags for given event. If there are delta flags to be // generated, call the asynchronous method to do so. CountingRequestMonitor multiRm = new CountingRequestMonitor(getExecutor(), requestMonitor); int buildDeltaForExpressionCallCount = 0; IExpression[] expressions = fManager.getExpressions(); for (int i = 0; i < expressions.length; i++ ) { int flags = getExpressionVMProvider().getDeltaFlagsForExpression(expressions[i], event); // If the given expression has no delta flags, skip it. if (flags == IModelDelta.NO_CHANGE) continue; int elementOffset = nodeOffset >= 0 ? nodeOffset + i : -1; getExpressionVMProvider().buildDeltaForExpression( expressions[i], elementOffset, event, parentDelta, getTreePathFromDelta(parentDelta), new RequestMonitor(getExecutor(), multiRm)); buildDeltaForExpressionCallCount++; } multiRm.setDoneCount(buildDeltaForExpressionCallCount); } } private void buildDeltaForExpressionsChangedEvent(ExpressionsChangedEvent event, VMDelta parentDelta, int nodeOffset, RequestMonitor requestMonitor) { CountingRequestMonitor multiRm = new CountingRequestMonitor(getExecutor(), requestMonitor); for (int i = 0; i < event.getExpressions().length; i++) { int expIndex = event.getIndex() != -1 ? nodeOffset + event.getIndex() + i : -1; getExpressionVMProvider().buildDeltaForExpression( event.getExpressions()[i], expIndex, event, parentDelta, getTreePathFromDelta(parentDelta), new RequestMonitor(getExecutor(), multiRm)); } multiRm.setDoneCount(event.getExpressions().length); } private TreePath getTreePathFromDelta(IModelDelta delta) { List<Object> elementList = new LinkedList<Object>(); IModelDelta listDelta = delta; elementList.add(0, listDelta.getElement()); while (listDelta.getParentDelta() != null) { elementList.add(0, listDelta.getElement()); listDelta = listDelta.getParentDelta(); } return new TreePath(elementList.toArray()); } /* * (non-Javadoc) * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IElementEditor#getCellEditor(org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, java.lang.String, java.lang.Object, org.eclipse.swt.widgets.Composite) */ @Override public CellEditor getCellEditor(IPresentationContext context, String columnId, Object element, Composite parent) { if (IDebugVMConstants.COLUMN_ID__EXPRESSION.equals(columnId) || (IDebugVMConstants.COLUMN_ID__NAME.equals(columnId) && !Arrays.asList(context.getColumns()).contains(IDebugVMConstants.COLUMN_ID__EXPRESSION)) ) { return new TextCellEditor(parent); } return null; } /* * (non-Javadoc) * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IElementEditor#getCellModifier(org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, java.lang.Object) */ @Override public ICellModifier getCellModifier(IPresentationContext context, Object element) { if ( fWatchExpressionCellModifier == null ) { fWatchExpressionCellModifier = createCellModifier(); } return fWatchExpressionCellModifier; } }