blob: 6b04cdee3c5b636cd8faec908c8412eb56d08dd8 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.debug.ui.assist;
import static org.eclipse.debug.ui.IDebugUIConstants.PREF_DETAIL_PANE_FONT;
import static org.eclipse.statet.ltk.ui.sourceediting.assist.InfoHover.MODE_FOCUS;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.AbstractInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IInformationControlExtension2;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.statet.ecommons.ui.SharedUIResources;
import org.eclipse.statet.ecommons.ui.actions.HandlerCollection;
import org.eclipse.statet.ecommons.ui.actions.SimpleContributionItem;
import org.eclipse.statet.ecommons.ui.util.InformationDispatchHandler;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.nico.core.runtime.ToolProcess;
import org.eclipse.statet.r.core.data.CombinedRElement;
import org.eclipse.statet.r.core.model.RElementName;
import org.eclipse.statet.r.ui.RLabelProvider;
import org.eclipse.statet.r.ui.dataeditor.RDataEditor;
import org.eclipse.statet.r.ui.dataeditor.RLiveDataEditorInput;
import org.eclipse.statet.r.ui.rtool.RElementInfoHoverCreator;
import org.eclipse.statet.r.ui.rtool.RElementInfoTask.RElementInfoData;
import org.eclipse.statet.rj.ts.core.RTool;
public class RElementInfoControl extends AbstractInformationControl implements IInformationControlExtension2,
IPropertyChangeListener {
private class OpenInEditorItem extends SimpleContributionItem {
public OpenInEditorItem() {
super(Messages.RElementInfo_OpenDataViewer_label, null,
PlatformUI.getWorkbench().getEditorRegistry().findEditor(RDataEditor.RDATA_EDITOR_ID)
.getImageDescriptor(), null );
}
@Override
public boolean isEnabled() {
final RElementInfoData input= getInput();
if (input != null) {
final CombinedRElement rElement= input.getElement();
return (rElement != null
&& RLiveDataEditorInput.isSupported(rElement)
&& input.getTool() instanceof ToolProcess );
}
return false;
}
@Override
protected void execute() throws ExecutionException {
final RElementInfoData input= getInput();
if (input != null) {
final RTool tool= input.getTool();
final RElementName elementName= input.getElementName();
RDataEditor.open(input.getWorkbenchPart().getSite().getPage(),
tool, elementName, null );
return;
}
}
}
private final int mode;
private RLabelProvider labelProvider;
private Composite contentComposite;
private Label titleImage;
private StyledText titleText;
private StyledText infoText;
private boolean layoutWorkaround;
private boolean layoutHint;
private RElementInfoData input;
private boolean inputChanged;
public RElementInfoControl(final Shell shell, final int mode) {
super(shell, ""); //$NON-NLS-1$
assert ((mode & MODE_FOCUS) == 0);
this.mode= mode;
JFaceResources.getFontRegistry().addListener(this);
create();
}
public RElementInfoControl(final Shell shell, final int mode, final boolean dummy) {
super(shell, new ToolBarManager(SWT.FLAT));
assert ((mode & MODE_FOCUS) != 0);
this.mode= mode;
create();
}
@Override
public void setInput(final Object input) {
this.inputChanged= true;
if (input instanceof RElementInfoData) {
this.input= (RElementInfoData) input;
}
else {
this.input= null;
}
}
public RElementInfoData getInput() {
return this.input;
}
@Override
public boolean hasContents() {
return (this.input != null);
}
@Override
protected void createContent(final Composite parent) {
this.contentComposite= new Composite(parent, SWT.NONE) {
@Override
public Point computeSize(final int width, final int height, final boolean changed) {
return super.computeSize(width, height, changed || width != getSize().x);
}
};
this.contentComposite.setBackgroundMode(SWT.INHERIT_FORCE);
final GridLayout gridLayout= LayoutUtils.newCompositeGrid(2);
gridLayout.horizontalSpacing= (int) ((gridLayout.horizontalSpacing) / 1.5);
this.contentComposite.setLayout(gridLayout);
final int vIndent= Math.max(1, LayoutUtils.defaultVSpacing() / 4);
final int hIndent= Math.max(3, LayoutUtils.defaultHSpacing() / 2);
{ // Title image
this.titleImage= new Label(this.contentComposite, SWT.NULL);
final Image image= SharedUIResources.getImages().get(SharedUIResources.PLACEHOLDER_IMAGE_ID);
this.titleImage.setImage(image);
final GridData textGd= new GridData(SWT.FILL, SWT.TOP, false, false);
this.titleText= new StyledText(this.contentComposite, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP) {
@Override
public Point computeSize(int width, final int height, final boolean changed) {
if (!RElementInfoControl.this.layoutHint && width <= 0 && RElementInfoControl.this.contentComposite.getSize().x > 0) {
width= RElementInfoControl.this.contentComposite.getSize().x -
LayoutUtils.defaultHMargin() - RElementInfoControl.this.titleImage.getSize().x - LayoutUtils.defaultHSpacing() - 10;
}
final Point size= super.computeSize(width, -1, true);
// if (width >= 0) {
// size.x= Math.min(size.x, width);
// }
return size;
}
};
this.titleText.setFont(JFaceResources.getDialogFont());
final GC gc= new GC(this.titleText);
final FontMetrics fontMetrics= gc.getFontMetrics();
final GridData imageGd= new GridData(SWT.FILL, SWT.TOP, false, false);
imageGd.horizontalIndent= hIndent;
final int textHeight= fontMetrics.getAscent() + fontMetrics.getLeading();
final int imageHeight= image.getBounds().height;
final int shift= Math.max(3, (int) ((fontMetrics.getDescent()) / 1.5));
if (textHeight+shift < imageHeight) {
imageGd.verticalIndent= vIndent+shift;
textGd.verticalIndent= vIndent+(imageHeight-textHeight);
}
else {
imageGd.verticalIndent= vIndent+(textHeight-imageHeight)+shift;
textGd.verticalIndent= vIndent;
}
this.titleImage.setLayoutData(imageGd);
this.titleText.setLayoutData(textGd);
this.layoutWorkaround= true;
gc.dispose();
}
this.infoText= new StyledText(this.contentComposite, ((this.mode & MODE_FOCUS) != 0) ?
(SWT.MULTI | SWT.READ_ONLY | SWT.V_SCROLL | SWT.H_SCROLL) :
(SWT.MULTI | SWT.READ_ONLY) );
this.infoText.setIndent(hIndent);
this.infoText.setFont(JFaceResources.getFont(PREF_DETAIL_PANE_FONT));
final GridData gd= new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
// gd.widthHint= LayoutUtils.hintWidth(fInfoText, INFO_FONT, 50);
this.infoText.setLayoutData(gd);
setBackgroundColor(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
setForegroundColor(getShell().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
if ((this.mode & MODE_FOCUS) != 0) {
final ToolBarManager toolBarManager= getToolBarManager();
contributeToActionBars(PlatformUI.getWorkbench(), toolBarManager, null);
}
updateInput();
}
protected void contributeToActionBars(final IServiceLocator serviceLocator,
final ToolBarManager toolBarManager, final HandlerCollection handlers) {
toolBarManager.add(new OpenInEditorItem());
}
@Override
public void setBackgroundColor(final Color background) {
super.setBackgroundColor(background);
this.contentComposite.setBackground(background);
}
@Override
public void setForegroundColor(final Color foreground) {
super.setForegroundColor(foreground);
this.contentComposite.setForeground(foreground);
this.titleText.setForeground(foreground);
this.infoText.setForeground(foreground);
}
@Override
public Rectangle computeTrim() {
final Rectangle trim= super.computeTrim();
final Rectangle textTrim= this.infoText.computeTrim(0, 0, 0, 0);
trim.x += textTrim.x;
trim.y += textTrim.y;
trim.width += textTrim.width;
trim.height += textTrim.height;
return trim;
}
@Override
public Point computeSizeHint() {
updateInput();
final Point sizeConstraints= getSizeConstraints();
final Rectangle trim= computeTrim();
// int charWidth= 20;
// if (fInput.detailInfo != null) {
// final int count= Math.min(6, fInfoText.getLineCount());
// for (int i= 0; i < count; i++) {
// charWidth= Math.max(charWidth, fInfoText.getLine(i).length());
// }
// }
int widthHint= this.infoText.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x + LayoutUtils.defaultHSpacing();
final int widthMax2= LayoutUtils.hintWidth(this.infoText, PREF_DETAIL_PANE_FONT, 80);
final int widthMax= ((sizeConstraints != null && sizeConstraints.x != SWT.DEFAULT) ?
sizeConstraints.x : widthMax2) - trim.width;
this.layoutHint= true;
final int titleHint= LayoutUtils.defaultHMargin() + this.titleImage.getSize().x + LayoutUtils.defaultHSpacing() + this.titleText.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
this.layoutHint= false;
if (titleHint > widthHint && widthMax2 > widthHint) {
widthHint= Math.min(widthMax2, titleHint);
}
if (widthMax < widthHint) {
widthHint= widthMax;
}
// avoid change of wrapping caused by scrollbar
if (widthHint < titleHint && widthHint + this.infoText.computeTrim(0, 0, 0, 0).width >= titleHint) {
widthHint= titleHint;
}
final int heightMax= ((sizeConstraints != null && sizeConstraints.y != SWT.DEFAULT) ?
sizeConstraints.y :
this.infoText.getLineHeight()*12) - trim.height;
final Point size= this.contentComposite.computeSize(widthHint, SWT.DEFAULT, true);
size.y += LayoutUtils.defaultVSpacing();
size.x= Math.max(Math.min(size.x, widthMax), 200) + trim.width;
size.y= Math.max(Math.min(size.y, heightMax), 100) + trim.height;
return size;
}
@Override
public Point computeSizeConstraints(final int widthInChars, final int heightInChars) {
final int titleWidth= LayoutUtils.hintWidth(this.titleText, JFaceResources.DIALOG_FONT, widthInChars);
final int titleHeight= this.titleText.getLineHeight();
final int infoWidth= LayoutUtils.hintWidth(this.infoText, PREF_DETAIL_PANE_FONT, widthInChars);
final int infoHeight= this.infoText.getLineHeight() * (heightInChars);
return new Point(Math.max(titleWidth, infoWidth),
titleHeight + LayoutUtils.defaultVSpacing() + infoHeight );
}
@Override
public void setVisible(final boolean visible) {
if (visible) {
updateInput();
if (this.layoutWorkaround) {
this.contentComposite.layout(true, true);
this.layoutWorkaround= false;
}
if (Platform.WS_WIN32.equals(SWT.getPlatform())) {
final Shell shell= getShell();
if (shell != null) {
shell.moveAbove(null);
}
}
}
super.setVisible(visible);
}
@Override
public void setFocus() {
this.infoText.setFocus();
}
private void updateInput() {
if (this.infoText == null || !this.inputChanged) {
return;
}
if (this.labelProvider == null) {
this.labelProvider= new RLabelProvider(RLabelProvider.LONG | RLabelProvider.HEADER | RLabelProvider.NAMESPACE);
}
if (this.input != null) {
final Image image= this.labelProvider.getImage(this.input.getElement());
this.titleImage.setImage((image != null) ? image : SharedUIResources.getImages().get(SharedUIResources.PLACEHOLDER_IMAGE_ID));
final StyledString styleString= this.labelProvider.getStyledText(this.input.getElement(), this.input.getElementName(), this.input.getElementAttr());
if (this.input.isElementOfActiveBinding()) {
styleString.append(" (active binding)", StyledString.QUALIFIER_STYLER);
}
this.titleText.setText(styleString.getString());
this.titleText.setStyleRanges(styleString.getStyleRanges());
if (this.input.hasDetail()) {
this.infoText.setText(this.input.getDetailTitle() + '\n' + ((this.input.getDetailInfo() != null) ? this.input.getDetailInfo() : "")); //$NON-NLS-1$
final StyleRange title= new StyleRange(0, this.input.getDetailTitle().length(), null, null);
title.underline= true;
this.infoText.setStyleRange(title);
}
else {
this.infoText.setText(""); //$NON-NLS-1$
}
}
else {
this.titleImage.setImage(SharedUIResources.getImages().get(SharedUIResources.PLACEHOLDER_IMAGE_ID));
this.titleText.setText(""); //$NON-NLS-1$
this.infoText.setText(""); //$NON-NLS-1$
}
if ((this.mode & MODE_FOCUS) != 0) {
getToolBarManager().update(true);
}
else {
setStatusText((this.input.getControl() != null
&& this.input.getControl().isFocusControl() ) ?
InformationDispatchHandler.getTooltipAffordanceString() : "" ); //$NON-NLS-1$
}
this.inputChanged= false;
}
@Override
public IInformationControlCreator getInformationPresenterControlCreator() {
// enriched mode
return new RElementInfoHoverCreator(this.mode | MODE_FOCUS);
}
@Override
public void propertyChange(final PropertyChangeEvent event) {
final String property= event.getProperty();
if (property.equals(PREF_DETAIL_PANE_FONT) || property.equals(JFaceResources.DEFAULT_FONT)) {
dispose();
}
}
@Override
public void dispose() {
JFaceResources.getFontRegistry().removeListener(this);
super.dispose();
}
}