| /*=============================================================================# |
| # 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); |
| } |
| |
| } |