blob: 5d4a81c8d763899e7f282592cf10dee37fedc52f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2014 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Wind River Systems - adapted for DSF
* Marc-Andre Laperle (Ericsson) - Remember hover size when expanded (Bug 417559)
*******************************************************************************/
package org.eclipse.cdt.dsf.debug.internal.ui;
import org.eclipse.cdt.dsf.debug.service.IExpressions.IExpressionDMContext;
import org.eclipse.cdt.dsf.debug.ui.IDsfDebugUIConstants;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.debug.internal.ui.SWTFactory;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputRequestor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.PresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ViewerInputService;
import org.eclipse.debug.internal.ui.views.variables.details.DefaultDetailPane;
import org.eclipse.debug.internal.ui.views.variables.details.DetailPaneProxy;
import org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer;
import org.eclipse.debug.ui.AbstractDebugView;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.text.AbstractInformationControl;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IInformationControlExtension2;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.themes.ITheme;
/**
* Creates an information control to display an expression in a hover control.
* <br/> This class is derivative work from JDT's <code>ExpressionInformationControlCreator</code>.
*
* @noextend This class is not intended to be subclassed by clients.
*
* @since 2.1
*/
@SuppressWarnings("restriction")
public class ExpressionInformationControlCreator implements IInformationControlCreator {
/**
* A presentation context for the expression hover control.
* Implements equals and hashCode based on id comparison.
*/
private static final class ExpressionHoverPresentationContext extends PresentationContext {
private ExpressionHoverPresentationContext(String id) {
super(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof ExpressionHoverPresentationContext) {
if (getId().equals(((PresentationContext) obj).getId())) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return getId().hashCode();
}
}
class ExpressionInformationControl extends AbstractInformationControl
implements IInformationControlExtension2, IViewerInputRequestor {
/**
* Dialog setting key for height
*/
private static final String HEIGHT = "HEIGHT"; //$NON-NLS-1$
/**
* Dialog setting key for width.
*/
private static final String WIDTH = "WIDTH"; //$NON-NLS-1$
/**
* Dialog setting key for tree sash weight
*/
private static final String SASH_WEIGHT_TREE = "SashWeightTree"; //$NON-NLS-1$
/**
* Dialog setting key for details sash weight
*/
private static final String SASH_WEIGHT_DETAILS = "SashWeightDetails"; //$NON-NLS-1$
/**
* Variable to display.
*/
private Object fVariable;
private TreeModelViewer fViewer;
private SashForm fSashForm;
private Composite fDetailPaneComposite;
private DetailPaneProxy fDetailPane;
private Tree fTree;
private IViewerUpdateListener fViewerUpdateListener;
private ViewerInputService fInputService;
private IInformationControlCreator fInformationControlCreator;
/**
* Inner class implementing IDetailPaneContainer methods. Handles changes to detail
* pane and provides limited access to the detail pane proxy.
*/
private class DetailPaneContainer implements IDetailPaneContainer {
/*
* @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getCurrentPaneID()
*/
@Override
public String getCurrentPaneID() {
return fDetailPane.getCurrentPaneID();
}
/*
* @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getCurrentSelection()
*/
@Override
public IStructuredSelection getCurrentSelection() {
return (IStructuredSelection) fViewer.getSelection();
}
/*
* @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#refreshDetailPaneContents()
*/
@Override
public void refreshDetailPaneContents() {
fDetailPane.display(getCurrentSelection());
}
/*
* @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getParentComposite()
*/
@Override
public Composite getParentComposite() {
return fDetailPaneComposite;
}
/*
* @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#getWorkbenchPartSite()
*/
@Override
public IWorkbenchPartSite getWorkbenchPartSite() {
return null;
}
/*
* @see org.eclipse.debug.internal.ui.views.variables.details.IDetailPaneContainer#paneChanged(java.lang.String)
*/
@Override
public void paneChanged(String newPaneID) {
if (DefaultDetailPane.ID.equals(newPaneID)) {
fDetailPane.getCurrentControl()
.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
}
}
}
/**
* Constructs a new control in the given shell.
*
* @param parentShell shell
* @param resize whether resize is supported
*/
ExpressionInformationControl(Shell parentShell, boolean resize) {
super(parentShell, resize);
create();
}
/**
* Override the size constraints so that it can be as big as
* when the hover was last displayed (IDialogSettings).
*/
@Override
public Point computeSizeConstraints(int widthInChars, int heightInChars) {
// Bug 417559: The TextViewerHoverManager constrains the size of a newly created
// ExpressionInformationControl by 100 chars by 12 chars (602x182). The control
// size can be expanded beyond that, however when re-created it will still be constrained.
// By removing the constraint in the presence of a non-null IDialogSettings,
// the size gets restored properly even when previously expanded.
Point dialogSettingsSize = getDialogSettingsSize();
if (dialogSettingsSize != null) {
return dialogSettingsSize;
}
return super.computeSizeConstraints(widthInChars, heightInChars);
}
@Override
public Point computeSizeHint() {
Point dialogSettingsSize = getDialogSettingsSize();
if (dialogSettingsSize != null) {
return dialogSettingsSize;
}
return super.computeSizeHint();
}
private Point getDialogSettingsSize() {
IDialogSettings settings = getDialogSettings(false);
if (settings != null) {
int x = getIntSetting(settings, WIDTH);
if (x > 0) {
int y = getIntSetting(settings, HEIGHT);
if (y > 0) {
return new Point(x, y);
}
}
}
return null;
}
@Override
public void setSize(int width, int height) {
if (!isResizable() && fDetailPaneComposite != null) {
// add height of details pane
height += fDetailPaneComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
}
super.setSize(width, height);
}
/**
* Returns the dialog settings for this hover or <code>null</code> if none
*
* @param create whether to create the settings
*/
private IDialogSettings getDialogSettings(boolean create) {
IDialogSettings settings = DsfUIPlugin.getDefault().getDialogSettings();
IDialogSettings section = settings.getSection(this.getClass().getName());
if (section == null & create) {
section = settings.addNewSection(this.getClass().getName());
}
return section;
}
/**
* Returns an integer value in the given dialog settings or -1 if none.
*
* @param settings dialog settings
* @param key key
* @return value or -1 if not present
*/
private int getIntSetting(IDialogSettings settings, String key) {
try {
return settings.getInt(key);
} catch (NumberFormatException e) {
return -1;
}
}
@Override
public void dispose() {
persistSettings(getShell());
super.dispose();
}
/**
* Persists dialog settings.
*
* @param shell
*/
private void persistSettings(Shell shell) {
if (shell != null && !shell.isDisposed()) {
if (isResizable()) {
IDialogSettings settings = getDialogSettings(true);
Point size = shell.getSize();
settings.put(WIDTH, size.x);
settings.put(HEIGHT, size.y);
int[] weights = fSashForm.getWeights();
if (weights.length == 1) {
settings.put(SASH_WEIGHT_TREE, weights[0]);
} else if (weights.length == 2) {
settings.put(SASH_WEIGHT_TREE, weights[0]);
settings.put(SASH_WEIGHT_DETAILS, weights[1]);
}
}
}
}
@Override
public void setVisible(boolean visible) {
if (!visible) {
if (fViewer != null && fViewerUpdateListener != null) {
// Remove update listener to avoid refreshing variables that were
// previously shown in a hover at every step
// Bug 438367
fViewer.removeViewerUpdateListener(fViewerUpdateListener);
fViewerUpdateListener = null;
}
persistSettings(getShell());
}
super.setVisible(visible);
}
@Override
protected void createContent(Composite parent) {
fSashForm = new SashForm(parent, parent.getStyle());
fSashForm.setOrientation(SWT.VERTICAL);
// update presentation context
AbstractDebugView view = getViewToEmulate();
IPresentationContext context = new ExpressionHoverPresentationContext(
IDsfDebugUIConstants.ID_EXPRESSION_HOVER);
if (view != null) {
// copy over properties
IPresentationContext copy = ((TreeModelViewer) view.getViewer()).getPresentationContext();
try {
String[] properties = copy.getProperties();
for (int i = 0; i < properties.length; i++) {
String key = properties[i];
context.setProperty(key, copy.getProperty(key));
}
} catch (NoSuchMethodError e) {
// ignore
}
}
fViewer = new TreeModelViewer(fSashForm, SWT.MULTI | SWT.VIRTUAL | SWT.FULL_SELECTION, context);
fViewer.setAutoExpandLevel(fExpansionLevel);
if (view != null) {
// copy over filters
StructuredViewer structuredViewer = (StructuredViewer) view.getViewer();
if (structuredViewer != null) {
ViewerFilter[] filters = structuredViewer.getFilters();
for (int i = 0; i < filters.length; i++) {
fViewer.addFilter(filters[i]);
}
}
}
fInputService = new ViewerInputService(fViewer, this);
fTree = fViewer.getTree();
if (fShowDetailPane) {
fDetailPaneComposite = SWTFactory.createComposite(fSashForm, 1, 1, GridData.FILL_BOTH);
Layout layout = fDetailPaneComposite.getLayout();
if (layout instanceof GridLayout) {
GridLayout gl = (GridLayout) layout;
gl.marginHeight = 0;
gl.marginWidth = 0;
}
fDetailPane = new DetailPaneProxy(new DetailPaneContainer());
fDetailPane.display(null); // Bring up the default pane so the user doesn't see an empty composite
fTree.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
fDetailPane.display((IStructuredSelection) fViewer.getSelection());
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
}
initSashWeights();
// add update listener to auto-select and display details of root expression
fViewerUpdateListener = new IViewerUpdateListener() {
@Override
public void viewerUpdatesComplete() {
fViewer.getDisplay().timerExec(100, () -> {
if (!fViewer.getTree().isDisposed()) {
TreeSelection selection = (TreeSelection) fViewer.getSelection();
if (selection.isEmpty()) {
selection = new TreeSelection(fViewer.getTopElementPath());
}
fViewer.setSelection(selection);
if (fDetailPane != null) {
fDetailPane.display(selection);
}
}
});
}
@Override
public void viewerUpdatesBegin() {
}
@Override
public void updateStarted(IViewerUpdate update) {
}
@Override
public void updateComplete(IViewerUpdate update) {
}
};
fViewer.addViewerUpdateListener(fViewerUpdateListener);
ITheme currentTheme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme();
ColorRegistry colorRegistry = currentTheme.getColorRegistry();
Color fg = colorRegistry.get(JFacePreferences.INFORMATION_FOREGROUND_COLOR);
if (fg == null) {
fg = getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND);
}
Color bg = colorRegistry.get(JFacePreferences.INFORMATION_BACKGROUND_COLOR);
if (bg == null) {
bg = getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND);
}
setForegroundColor(fg);
setBackgroundColor(bg);
}
/**
* Attempts to find an appropriate view to emulate, this will either be the
* variables view or the expressions view.
* @return a view to emulate or <code>null</code>
*/
private AbstractDebugView getViewToEmulate() {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
AbstractDebugView expressionsView = (AbstractDebugView) page.findView(IDebugUIConstants.ID_EXPRESSION_VIEW);
if (expressionsView != null && expressionsView.isVisible()) {
return expressionsView;
}
AbstractDebugView variablesView = (AbstractDebugView) page.findView(IDebugUIConstants.ID_VARIABLE_VIEW);
if (variablesView != null && variablesView.isVisible()) {
return variablesView;
}
if (expressionsView != null) {
return expressionsView;
}
return variablesView;
}
/**
* Initializes the sash form weights from the preference store (using default values if
* no sash weights were stored previously).
*/
protected void initSashWeights() {
IDialogSettings settings = getDialogSettings(false);
if (settings != null) {
int tree = getIntSetting(settings, SASH_WEIGHT_TREE);
if (tree > 0) {
if (fDetailPane != null) {
int details = getIntSetting(settings, SASH_WEIGHT_DETAILS);
if (details <= 0) {
details = tree / 2;
}
fSashForm.setWeights(new int[] { tree, details });
} else {
fSashForm.setWeights(new int[] { tree });
}
}
}
}
@Override
public void setForegroundColor(Color foreground) {
super.setForegroundColor(foreground);
if (fDetailPaneComposite != null) {
fDetailPaneComposite.setForeground(foreground);
}
fTree.setForeground(foreground);
}
@Override
public void setBackgroundColor(Color background) {
super.setBackgroundColor(background);
if (fDetailPaneComposite != null) {
fDetailPaneComposite.setBackground(background);
}
fTree.setBackground(background);
}
@Override
public void setFocus() {
super.setFocus();
fTree.setFocus();
}
/*
* @see org.eclipse.jface.text.IInformationControlExtension#hasContents()
*/
@Override
public boolean hasContents() {
return fVariable != null;
}
/*
* @see org.eclipse.jface.text.IInformationControlExtension2#setInput(java.lang.Object)
*/
@Override
public void setInput(Object input) {
if (input instanceof IExpressionDMContext) {
fVariable = input;
fInputService.resolveViewerInput(input);
}
}
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
if (fInformationControlCreator == null) {
fInformationControlCreator = new ExpressionInformationControlCreator(fShowDetailPane, fExpansionLevel) {
@Override
public IInformationControl createInformationControl(Shell shell) {
return new ExpressionInformationControl(shell, true);
}
};
}
return fInformationControlCreator;
}
@Override
public void viewerInputComplete(IViewerInputUpdate update) {
fViewer.setInput(fVariable = update.getInputElement());
}
}
protected final boolean fShowDetailPane;
protected final int fExpansionLevel;
/**
* Create default expression information control creator.
* <p>
* Same as {@code ExpressionInformationControlCreator(true, 1)}.
* </p>
*/
public ExpressionInformationControlCreator() {
this(true, 1);
}
/**
* Create expression information control creator with customization options.
*
* @param showDetailPane if <code>true</code> the detail pane will be shown
* @param expansionLevel tree level to which the expression should be expanded by default
*/
public ExpressionInformationControlCreator(boolean showDetailPane, int expansionLevel) {
fShowDetailPane = showDetailPane;
fExpansionLevel = expansionLevel;
}
/*
* @see org.eclipse.jface.text.IInformationControlCreator#createInformationControl(org.eclipse.swt.widgets.Shell)
*/
@Override
public IInformationControl createInformationControl(Shell parent) {
return new ExpressionInformationControl(parent, false);
}
}