blob: 15d5ae004829f608615c9f32af30ff7c5aa3a871 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2015 Wind River Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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<>(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<>();
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());
}
@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;
}
@Override
public ICellModifier getCellModifier(IPresentationContext context, Object element) {
if (fWatchExpressionCellModifier == null) {
fWatchExpressionCellModifier = createCellModifier();
}
return fWatchExpressionCellModifier;
}
}