blob: 89c128957cd5c96b08451cf99cdcd97b942cad07 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2021 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.rj.eclient.graphics;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
/**
* Composite to display an R graphic.
*/
public class RGraphicComposite extends Composite
implements ERGraphic.ListenerInstructionsExtension, ERGraphic.ListenerLocatorExtension {
/**
* This class provides the layout for RGraphicComposite
*/
private static class ScrolledCompositeLayout extends Layout {
static final int DEFAULT_WIDTH = 64;
static final int DEFAULT_HEIGHT = 64;
private boolean inLayout= false;
public ScrolledCompositeLayout() {
}
@Override
protected boolean flushCache(final Control control) {
return true;
}
@Override
protected Point computeSize(final Composite composite, final int wHint, final int hHint, final boolean flushCache) {
final RGraphicComposite sc= (RGraphicComposite) composite;
final Point size= new Point(wHint, hHint);
if (wHint == SWT.DEFAULT) {
size.x= Math.max(DEFAULT_WIDTH, sc.width);
}
if (hHint == SWT.DEFAULT) {
size.y= Math.max(DEFAULT_HEIGHT, sc.height);
}
return size;
}
@Override
protected void layout(final Composite composite, final boolean flushCache) {
if (this.inLayout || composite == null) {
return;
}
final RGraphicComposite sc= (RGraphicComposite) composite;
final ScrollBar hBar= sc.getHorizontalBar();
final ScrollBar vBar= sc.getVerticalBar();
if (hBar.getSize().y >= sc.getSize().y) {
return;
}
if (vBar.getSize().x >= sc.getSize().x) {
return;
}
this.inLayout= true;
final Rectangle previousBounds= sc.canvas.getBounds();
boolean hVisible= sc.needHBar(previousBounds, false);
final boolean vVisible= sc.needVBar(previousBounds, hVisible);
if (!hVisible && vVisible) {
hVisible= sc.needHBar(previousBounds, vVisible);
}
hBar.setVisible(hVisible);
vBar.setVisible(vVisible);
final Rectangle clientArea= sc.getClientArea();
final int x;
final int y;
final int width= sc.width;
final int height= sc.height;
hBar.setThumb(clientArea.width);
hBar.setPageIncrement(clientArea.width);
if (hVisible) {
x= (sc.changedLayout || previousBounds.x >= 0) ? 0 :
Math.max(previousBounds.x, clientArea.width - width);
hBar.setSelection(-x);
}
else {
x= (clientArea.width - width) / 2;
hBar.setSelection(0);
}
vBar.setThumb(clientArea.height);
vBar.setPageIncrement(clientArea.height);
if (vVisible) {
y= (sc.changedLayout || previousBounds.y >= 0) ? 0 :
Math.max(previousBounds.y, clientArea.height - height);
vBar.setSelection(-y);
}
else {
y= (clientArea.height - height) / 2;
vBar.setSelection(0);
}
sc.changedLayout= false;
sc.canvas.setBounds(x, y, width, height);
this.inLayout= false;
}
}
private class PanListener implements Listener {
private boolean started;
private Point startMouse;
@Override
public void handleEvent(final Event event) {
switch (event.type) {
case SWT.MouseDown:
if (event.button == 2) {
this.started= true;
updateCursor();
this.startMouse= checkedPoint(event);
return;
}
else {
this.started= false;
return;
}
case SWT.MouseExit:
case SWT.MouseMove:
if (this.started) {
pan(checkedPoint(event));
return;
}
else {
return;
}
case SWT.MouseUp:
if (this.started && event.button == 2) {
this.started= false;
updateCursor();
return;
}
else {
return;
}
case SWT.KeyDown:
switch (event.keyCode) {
case SWT.ARROW_DOWN:
if (update(getVerticalBar(), RGraphicComposite.this.panIncrement)) {
scrollV();
}
return;
case SWT.ARROW_UP:
if (update(getVerticalBar(), -RGraphicComposite.this.panIncrement)) {
scrollV();
}
return;
case SWT.ARROW_RIGHT:
if (update(getHorizontalBar(), RGraphicComposite.this.panIncrement)) {
scrollH();
}
return;
case SWT.ARROW_LEFT:
if (update(getHorizontalBar(), -RGraphicComposite.this.panIncrement)) {
scrollH();
}
return;
default:
return;
}
}
}
private Point checkedPoint(final Event event) {
if (event.item == RGraphicComposite.this) {
return new Point(event.x, event.y);
}
else {
final Point point= ((Control) event.widget).toDisplay(event.x, event.y);
return RGraphicComposite.this.toControl(point);
}
}
private void pan(final Point point) {
if (update(getHorizontalBar(), this.startMouse.x - point.x)) {
scrollH();
}
if (update(getVerticalBar(), this.startMouse.y - point.y)) {
scrollV();
}
this.startMouse= point;
}
private boolean update(final ScrollBar bar, final int step) {
if (bar != null && bar.isVisible()) {
int selection= bar.getSelection() + step;
if (selection < 0) {
selection= 0;
}
else if (selection > bar.getMaximum()) {
selection= bar.getMaximum() - bar.getThumb();
}
bar.setSelection(selection);
return true;
}
return false;
}
}
private static int checkStyle (final int style) {
final int mask= SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
return style & mask;
}
private ERGraphic graphic;
private RGraphicCanvas canvas;
private int width= 0;
private int height= 0;
private boolean changedLayout;
private boolean changedContent;
private int panIncrement;
private final PanListener panListener;
private MouseListener locatorListener;
public RGraphicComposite(final Composite parent, final ERGraphic graphic) {
super(parent, checkStyle(SWT.H_SCROLL | SWT.V_SCROLL));
super.setLayout(new ScrolledCompositeLayout());
this.panIncrement= 10;
final ScrollBar hBar= getHorizontalBar();
hBar.setVisible(false);
hBar.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(final Event event) {
scrollH();
}
});
hBar.setIncrement(this.panIncrement);
final ScrollBar vBar= getVerticalBar();
vBar.setVisible(false);
vBar.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(final Event event) {
scrollV();
}
});
vBar.setIncrement(this.panIncrement);
addListener(SWT.Resize, new Listener() {
@Override
public void handleEvent(final Event event) {
// checkContentSize();
}
});
createCanvas();
final Listener updateListener= new Listener() {
@Override
public void handleEvent(final Event event) {
if (RGraphicComposite.this.changedContent) {
updateGraphic();
}
}
};
addListener(SWT.Show, updateListener);
addListener(SWT.Activate, updateListener);
this.panListener= new PanListener();
addListener(SWT.MouseDown, this.panListener);
addListener(SWT.MouseMove, this.panListener);
addListener(SWT.MouseExit, this.panListener);
addListener(SWT.MouseUp, this.panListener);
addListener(SWT.KeyDown, this.panListener);
this.canvas.addListener(SWT.MouseDown, this.panListener);
this.canvas.addListener(SWT.MouseMove, this.panListener);
this.canvas.addListener(SWT.MouseExit, this.panListener);
this.canvas.addListener(SWT.MouseUp, this.panListener);
this.canvas.addListener(SWT.KeyDown, this.panListener);
setGraphic(graphic);
addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(final DisposeEvent e) {
disconnect();
}
});
}
private void createCanvas() {
this.canvas= new RGraphicCanvas(this);
}
boolean needHBar(final Rectangle contentRect, final boolean vVisible) {
final Rectangle hostRect= getBounds();
hostRect.width-= 2 * getBorderWidth();
if (vVisible) {
hostRect.width-= getVerticalBar().getSize().x;
}
return (this.width > hostRect.width);
}
boolean needVBar(final Rectangle contentRect, final boolean hVisible) {
final Rectangle hostRect= getBounds();
hostRect.height-= 2 * getBorderWidth();
if (hVisible) {
hostRect.height-= getHorizontalBar().getSize().y;
}
return (this.height > hostRect.height);
}
private void scrollH() {
final Point location= this.canvas.getLocation();
final ScrollBar hBar= getHorizontalBar();
if (hBar != null && hBar.isVisible()) {
final int hSelection= hBar.getSelection();
this.canvas.setLocation(-hSelection, location.y);
}
}
private void scrollV() {
final Point location= this.canvas.getLocation();
final ScrollBar vBar= getVerticalBar();
if (vBar != null && vBar.isVisible()) {
final int vSelection= vBar.getSelection();
this.canvas.setLocation(location.x, -vSelection);
}
}
protected void disconnect() {
if (this.graphic != null) {
this.graphic.removeListener(this);
}
locatorStopped();
}
public void setGraphic(final ERGraphic graphic) {
disconnect();
this.graphic= graphic;
if (this.graphic != null) {
this.graphic.addListener(this);
}
instructionsChanged(true, null);
if (this.graphic != null && this.graphic.isLocatorStarted()) {
locatorStarted();
}
}
/**
* {@inheritDoc}
* <p>
* Note: No Layout can be set on this Control because it already
* manages the size and position of its children.</p>
*/
@Override
public void setLayout (final Layout layout) {
checkWidget();
return;
}
@Override
public void activated() {
}
@Override
public void deactivated() {
}
@Override
public void drawingStarted() {
}
@Override
public void instructionsChanged(final boolean reset, final List<ERGraphicInstruction> added) {
this.changedContent= true;
if (isVisible()) {
updateGraphic();
}
}
@Override
public void drawingStopped() {
}
@Override
public void locatorStarted() {
if (this.locatorListener == null) {
this.locatorListener= new MouseListener() {
@Override
public void mouseDown(final MouseEvent e) {
switch (e.button) {
case 1:
RGraphicComposite.this.graphic.returnLocator(
RGraphicComposite.this.canvas.widget2graphicsX(e.x), RGraphicComposite.this.canvas.widget2graphicY(e.y));
break;
case 3:
RGraphicComposite.this.graphic.stopLocator(ERGraphic.LOCATOR_DONE);
break;
default:
break;
}
}
@Override
public void mouseUp(final MouseEvent e) {
}
@Override
public void mouseDoubleClick(final MouseEvent e) {
}
};
this.canvas.addMouseListener(this.locatorListener);
updateCursor();
}
}
@Override
public void locatorStopped() {
if (this.locatorListener != null) {
this.canvas.removeMouseListener(this.locatorListener);
this.locatorListener= null;
updateCursor();
}
}
private void updateCursor() {
if (this.panListener.started) {
this.canvas.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND));
}
else if (this.locatorListener != null) {
this.canvas.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_CROSS));
}
else {
this.canvas.setCursor(null);
}
}
private void checkContentSize() {
final Point size= this.canvas.computeSize(SWT.DEFAULT, SWT.DEFAULT);
if (this.width == size.x && this.height == size.y) {
return;
}
this.changedLayout= true;
this.width= size.x;
this.height= size.y;
this.canvas.setSize(this.width, this.height);
final ScrollBar hBar= getHorizontalBar();
hBar.setMaximum(this.width);
final ScrollBar vBar= getVerticalBar();
vBar.setMaximum(this.height);
layout(false);
}
public void redrawGraphic() {
this.canvas.redraw();
}
private void updateGraphic() {
this.changedContent= false;
final List<? extends ERGraphicInstruction> instructions= (this.graphic != null) ?
this.graphic.getInstructions() : RGraphicCanvas.NO_GRAPHIC;
this.canvas.setInstructions(instructions);
checkContentSize();
this.canvas.redraw();
}
public double[] getGraphicFitSize() {
final Rectangle bounds= getBounds();
return new double[] { bounds.width, bounds.height };
}
public Control getControl() {
return this;
}
/**
* Returns the control on which the graphic is painted
*
* @since 1.0
*/
public Control getGraphicWidget() {
return this.canvas;
}
/**
* Converts a horizontal display coordinate of the {@link #getGraphicWidget() graphic widget}
* to its graphic coordinate value.
*
* @since 1.0
* @see ERGraphic Coordinate systems of R graphics
*/
public double convertWidget2GraphicX(final int x) {
return this.canvas.widget2graphicsX(x);
}
/**
* Converts a vertical display coordinate of the {@link #getGraphicWidget() graphic widget}
* to its graphic coordinate value.
*
* @since 1.0
* @see ERGraphic Coordinate systems of R graphics
*/
public double convertWidget2GraphicY(final int y) {
return this.canvas.widget2graphicY(y);
}
}