| /*=============================================================================# |
| # Copyright (c) 2009, 2019 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.rj.eclient.graphics; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.concurrent.Callable; |
| |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.preferences.IPreferencesService; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.ImageData; |
| import org.eclipse.swt.graphics.PaletteData; |
| import org.eclipse.swt.graphics.Path; |
| import org.eclipse.swt.widgets.Display; |
| |
| import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet; |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.runtime.CommonsRuntime; |
| import org.eclipse.statet.jcommons.status.ErrorStatus; |
| import org.eclipse.statet.jcommons.status.InfoStatus; |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.Status; |
| import org.eclipse.statet.jcommons.status.StatusChangeListener; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.status.Statuses; |
| |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| |
| import org.eclipse.statet.internal.rj.eclient.graphics.FontManager.FontFamily; |
| import org.eclipse.statet.rj.eclient.graphics.DefaultGCRenderer; |
| import org.eclipse.statet.rj.eclient.graphics.ERGraphic; |
| import org.eclipse.statet.rj.eclient.graphics.ERGraphicInstruction; |
| import org.eclipse.statet.rj.eclient.graphics.LocatorCallback; |
| import org.eclipse.statet.rj.eclient.graphics.RGraphics; |
| import org.eclipse.statet.rj.eclient.graphics.comclient.ToolRClientGraphicActions; |
| import org.eclipse.statet.rj.graphic.core.RGraphicInitialization; |
| import org.eclipse.statet.rj.graphic.core.RGraphicInstruction; |
| import org.eclipse.statet.rj.graphic.core.util.CachedMapping; |
| import org.eclipse.statet.rj.graphic.core.util.CharMapping; |
| import org.eclipse.statet.rj.graphic.core.util.Unicode2AdbSymbolMapping; |
| import org.eclipse.statet.rj.server.client.RClientGraphic; |
| import org.eclipse.statet.rj.server.client.RClientGraphicFactory; |
| import org.eclipse.statet.rj.services.RService; |
| import org.eclipse.statet.rj.services.RServiceControlExtension; |
| import org.eclipse.statet.rj.ts.core.RTool; |
| |
| |
| /** |
| * R graphic implementation of this plug-in. Implements R side API ({@link RClientGraphic}) |
| * as well as client side API ({@link ERGraphic}, {@link org.eclipse.statet.rj.graphic.core.RGraphic}). |
| */ |
| public class EclipseRGraphic implements RClientGraphic, ERGraphic { |
| |
| |
| private static final CharMapping ADBSYMBOL_MAPPING= new CachedMapping( |
| new Unicode2AdbSymbolMapping() ); |
| |
| private static final long MILLI_NANOS= 1000000L; |
| |
| private static final int DIRECT_RED_MASK= 0x0000FF00; |
| private static final int DIRECT_GREEN_MASK= 0x00FF0000; |
| private static final int DIRECT_BLUE_MASK= 0xFF000000; |
| private static final PaletteData DIRECT_PALETTE= new PaletteData( |
| DIRECT_RED_MASK, DIRECT_GREEN_MASK, DIRECT_BLUE_MASK ); |
| |
| private static final LocatorCallback R_LOCATOR_CALLBACK= new LocatorCallback() { |
| |
| @Override |
| public String getMessage() { |
| return "→ Locate a point by mouse click (request from R)"; |
| } |
| |
| @Override |
| public int located(final double x, final double y) { |
| return NEXT; |
| } |
| |
| @Override |
| public void stopped(final String type) { |
| } |
| |
| }; |
| |
| |
| private static void disposeElements(final ERGraphicInstruction[] instructions, |
| final int beginIdx, final int endIdx) { |
| try { |
| for (int i= beginIdx; i < endIdx; i++) { |
| switch (instructions[i].getInstructionType()) { |
| case RGraphicInstruction.DRAW_RASTER: { |
| final Image image= ((RasterElement) instructions[i]).swtImage; |
| if (image != null && !image.isDisposed()) { |
| image.dispose(); |
| } |
| continue; } |
| case RGraphicInstruction.DRAW_PATH: { |
| final Path path= ((PathElement) instructions[i]).swtPath; |
| if (path != null && !path.isDisposed()) { |
| path.dispose(); |
| } |
| continue; } |
| default: |
| continue; |
| } |
| } |
| } |
| catch (final SWTException e) { |
| if (e.code != SWT.ERROR_DEVICE_DISPOSED) { |
| CommonsRuntime.log(new ErrorStatus(RGraphics.BUNDLE_ID, |
| "An error occurred when disposing SWT resources.", |
| e )); |
| } |
| } |
| } |
| |
| private static class DisposeRunnable implements Runnable { |
| |
| private final ERGraphicInstruction[] instructions; |
| private final int size; |
| |
| private boolean delay; |
| |
| public DisposeRunnable(final ERGraphicInstruction[] instructions, final int size) { |
| this.instructions= instructions; |
| this.size= size; |
| } |
| |
| @Override |
| public void run() { |
| if (this.delay) { |
| this.delay= false; |
| Display.getCurrent().timerExec(5000, this); |
| } |
| |
| disposeElements(this.instructions, 0, this.size); |
| } |
| |
| } |
| |
| |
| private final int devId; |
| |
| private int canvasColor; |
| |
| private int drawingStopDelay= 33; // ms after stop |
| private int drawingForceDelay= 333; // ms after last update |
| |
| private final EclipseRGraphicFactory manager; |
| private boolean isRClosed; |
| private boolean isLocalClosed; |
| |
| private final Object stateLock= new Object(); |
| private boolean isActive; |
| private boolean isActiveNotified; |
| private int mode= 1; |
| private int modeNotified; |
| private long drawingStoppedStamp; |
| private long instructionsNotifiedStamp; |
| private boolean stateNotificationDirectScheduled; |
| private boolean stateNotificationDelayedScheduled; |
| private final Runnable stateNotificationRunnable= new Runnable() { |
| @Override |
| public void run() { |
| int type= 0; |
| Runnable runnable= null; |
| try { |
| while (true) { |
| boolean reset= false; |
| List<ERGraphicInstruction> update= null; |
| synchronized (EclipseRGraphic.this.stateLock) { |
| if (type == 0 |
| && !EclipseRGraphic.this.stateNotificationDirectScheduled |
| && EclipseRGraphic.this.stateNotificationDelayedScheduled) { |
| EclipseRGraphic.this.stateNotificationDirectScheduled= true; |
| EclipseRGraphic.this.stateNotificationDelayedScheduled= false; |
| } |
| if (EclipseRGraphic.this.isActive != EclipseRGraphic.this.isActiveNotified) { |
| EclipseRGraphic.this.isActiveNotified= EclipseRGraphic.this.isActive; |
| type= (EclipseRGraphic.this.isActive) ? 1 : 2; |
| } |
| else if ((EclipseRGraphic.this.mode == 1 || EclipseRGraphic.this.instructionsUpdateSize > 0) |
| && EclipseRGraphic.this.modeNotified != 1 ) { |
| // start |
| EclipseRGraphic.this.modeNotified= 1; |
| type= 3; |
| } |
| else if (EclipseRGraphic.this.instructionsUpdateSize > 0) { |
| // update |
| final long stamp= System.nanoTime(); |
| int t= EclipseRGraphic.this.drawingForceDelay - (int) ((stamp - EclipseRGraphic.this.instructionsNotifiedStamp) / MILLI_NANOS); |
| if (t > 10 && EclipseRGraphic.this.mode != 1) { |
| t= Math.min(t, EclipseRGraphic.this.drawingStopDelay - (int) ((stamp - EclipseRGraphic.this.drawingStoppedStamp) / MILLI_NANOS)); |
| } |
| if (t <= 10) { |
| reset= (EclipseRGraphic.this.instructionsUpdateStart == 0); |
| |
| synchronized (EclipseRGraphic.this.instructionsLock) { |
| if (reset && EclipseRGraphic.this.instructionsSize > 0) { |
| runnable= new DisposeRunnable(EclipseRGraphic.this.instructions, EclipseRGraphic.this.instructionsSize); |
| } |
| EclipseRGraphic.this.instructions= EclipseRGraphic.this.instructionsUpdate; |
| EclipseRGraphic.this.instructionsSize= EclipseRGraphic.this.instructionsUpdateStart + EclipseRGraphic.this.instructionsUpdateSize; |
| } |
| update= ImCollections.newList(EclipseRGraphic.this.instructionsUpdate) |
| .subList(EclipseRGraphic.this.instructionsUpdateStart, EclipseRGraphic.this.instructionsUpdateStart + EclipseRGraphic.this.instructionsUpdateSize); |
| // System.out.println("InstrUpdate: \treset= " + reset + " \tcount= " + update.size() + " \ttdiff= " + ((stamp - this.instructionsNotifiedStamp) / MILLI_NANOS)); |
| EclipseRGraphic.this.instructionsUpdateStart= EclipseRGraphic.this.instructionsSize; |
| EclipseRGraphic.this.instructionsUpdateSize= 0; |
| EclipseRGraphic.this.instructionsNotifiedStamp= stamp; |
| type= 5; |
| } |
| else { |
| if (!EclipseRGraphic.this.stateNotificationDelayedScheduled) { |
| EclipseRGraphic.this.stateNotificationDelayedScheduled= true; |
| EclipseRGraphic.this.display.timerExec(10 + Math.min(t, EclipseRGraphic.this.drawingStopDelay), this); |
| } |
| EclipseRGraphic.this.stateNotificationDirectScheduled= false; |
| type= 0; |
| return; |
| } |
| } |
| else if (EclipseRGraphic.this.mode != 1 && EclipseRGraphic.this.modeNotified == 1 ) { |
| // stop |
| final long stamp= System.nanoTime(); |
| final int t= EclipseRGraphic.this.drawingStopDelay - (int) ((stamp - EclipseRGraphic.this.drawingStoppedStamp) / MILLI_NANOS); |
| if (t <= 10) { |
| EclipseRGraphic.this.modeNotified= 0; |
| type= 4; |
| } |
| else { |
| if (!EclipseRGraphic.this.stateNotificationDelayedScheduled) { |
| EclipseRGraphic.this.stateNotificationDelayedScheduled= true; |
| EclipseRGraphic.this.display.timerExec(10 + t, this); |
| } |
| EclipseRGraphic.this.stateNotificationDirectScheduled= false; |
| type= 0; |
| return; |
| } |
| } |
| else { |
| // done |
| EclipseRGraphic.this.stateNotificationDirectScheduled= false; |
| type= 0; |
| return; |
| } |
| } |
| |
| |
| switch (type) { |
| case 1: |
| for (final Listener listener : EclipseRGraphic.this.graphicListeners.toList()) { |
| listener.activated(); |
| } |
| break; |
| case 2: |
| for (final Listener listener : EclipseRGraphic.this.graphicListeners.toList()) { |
| listener.deactivated(); |
| } |
| break; |
| case 3: |
| for (final Listener listener : EclipseRGraphic.this.graphicListeners.toList()) { |
| listener.drawingStarted(); |
| } |
| break; |
| case 4: |
| for (final Listener listener : EclipseRGraphic.this.graphicListeners.toList()) { |
| listener.drawingStopped(); |
| } |
| break; |
| case 5: |
| for (final Listener listener : EclipseRGraphic.this.graphicListeners.toList()) { |
| if (listener instanceof ListenerInstructionsExtension) { |
| ((ListenerInstructionsExtension) listener).instructionsChanged(reset, update); |
| } |
| } |
| // System.out.println("InstrNotif: ns= " + (System.nanoTime() - this.instructionsNotifiedStamp)); |
| break; |
| } |
| } |
| } |
| finally { |
| if (type != 0) { |
| synchronized (EclipseRGraphic.this.stateLock) { |
| EclipseRGraphic.this.stateNotificationDirectScheduled= false; |
| } |
| } |
| if (runnable != null) { |
| execInDisplay(runnable); |
| } |
| } |
| } |
| }; |
| |
| |
| private final int options; |
| private final ToolRClientGraphicActions actions; |
| private volatile double[] nextSize; |
| private double[] size; |
| |
| private FontFamily currentFontFamily; |
| private int currentFontSize; |
| private int currentFontStyle; |
| private int currentFontRFace; |
| private CharMapping currentFontMapping; |
| |
| private String lastStringEnc; |
| private double[] lastStringWidth; |
| |
| private final Display display; |
| private boolean isDisposed; |
| private final FontManager swtFontManager; |
| private final ColorManager swtColorManager; |
| |
| private String serifFontName; |
| private String sansFontName; |
| private String monoFontName; |
| private String symbolFontName; |
| private CharMapping symbolFontMapping; |
| |
| /** List of newly added instructions */ |
| private ERGraphicInstruction[] instructionsNew= new ERGraphicInstruction[1]; // initial init |
| /** Count of newly added instructions in {@link #instructionsNew} */ |
| private int instructionsNewSize; |
| /** Current list of instructions, notified + not yet notified */ |
| private ERGraphicInstruction[] instructionsUpdate; |
| /** Index of first not yet notified instruction in {@link #instructionsUpdate} */ |
| private int instructionsUpdateStart; |
| /** Count of not yet notified instructions in {@link #instructionsUpdate} */ |
| private int instructionsUpdateSize; |
| /** Lock for {@link #instructions} and {@link #instructionsSize} */ |
| private final Object instructionsLock= new Object(); |
| /** List of notified instructions */ |
| private ERGraphicInstruction[] instructions; |
| /** Count of notified instructions in {@link #instructions} */ |
| private int instructionsSize; |
| |
| private final Object userExchangeLock= new Object(); |
| private String userExchangeRType; |
| private RServiceControlExtension userExchangeRCallback; |
| |
| private volatile LocatorCallback locatorCallback; // != null => started |
| private LocatorCallback locatorNotified; |
| private Status locatorMessage; |
| private Collection<String> locatorStopTypes= Collections.emptySet(); |
| private double[] locatorLocationValue; // only used for R |
| private final Object locatorAnswerLock= new Object(); // pipe for answers |
| |
| private final CopyOnWriteIdentityListSet<ERGraphic.Listener> graphicListeners= new CopyOnWriteIdentityListSet<>(); |
| |
| private Status message= Statuses.OK_STATUS; |
| private final CopyOnWriteIdentityListSet<StatusChangeListener> messageListeners= new CopyOnWriteIdentityListSet<>(); |
| |
| private boolean locatorNotificationDirectScheduled; |
| private boolean locatorNotificationDeferredScheduled; |
| private long locatorDeferredStamp; |
| private final Runnable locatorNotificationRunnable= new Runnable() { |
| @Override |
| public void run() { |
| int type= 0; |
| try { |
| while (true) { |
| synchronized (EclipseRGraphic.this.userExchangeLock) { |
| if (type == 0 |
| && !EclipseRGraphic.this.locatorNotificationDirectScheduled |
| && EclipseRGraphic.this.locatorNotificationDeferredScheduled) { |
| EclipseRGraphic.this.locatorNotificationDirectScheduled= true; |
| EclipseRGraphic.this.locatorNotificationDeferredScheduled= false; |
| } |
| if (EclipseRGraphic.this.locatorCallback != null && EclipseRGraphic.this.locatorNotified == null |
| && EclipseRGraphic.this.locatorDeferredStamp == Long.MIN_VALUE) { |
| EclipseRGraphic.this.locatorNotified= EclipseRGraphic.this.locatorCallback; |
| type= 1; |
| } |
| else if (EclipseRGraphic.this.locatorNotified != null |
| && (EclipseRGraphic.this.locatorCallback != EclipseRGraphic.this.locatorNotified || EclipseRGraphic.this.locatorCallback == null) ){ |
| EclipseRGraphic.this.locatorNotified= null; |
| type= 2; |
| } |
| else if (EclipseRGraphic.this.locatorDeferredStamp != Long.MIN_VALUE |
| && EclipseRGraphic.this.locatorCallback != null) { |
| final int t= (int) ((EclipseRGraphic.this.locatorDeferredStamp - System.nanoTime()) / 1000000); |
| if (t <= 10) { |
| internalStopLocator(false); |
| type= 3; |
| continue; |
| } |
| else { |
| if (!EclipseRGraphic.this.locatorNotificationDeferredScheduled) { |
| EclipseRGraphic.this.locatorNotificationDeferredScheduled= true; |
| EclipseRGraphic.this.display.timerExec(t + 10, this); |
| } |
| EclipseRGraphic.this.locatorNotificationDirectScheduled= false; |
| type= 0; |
| return; |
| } |
| } |
| else { |
| EclipseRGraphic.this.locatorNotificationDirectScheduled= false; |
| type= 0; |
| return; |
| } |
| } |
| |
| switch (type) { |
| case 1: |
| for (final Listener listener : EclipseRGraphic.this.graphicListeners.toList()) { |
| if (listener instanceof ListenerLocatorExtension) { |
| ((ListenerLocatorExtension) listener).locatorStarted(); |
| } |
| } |
| break; |
| case 2: |
| for (final Listener listener : EclipseRGraphic.this.graphicListeners.toList()) { |
| if (listener instanceof ListenerLocatorExtension) { |
| ((ListenerLocatorExtension) listener).locatorStopped(); |
| } |
| } |
| break; |
| } |
| updateMessage(); |
| } |
| } |
| finally { |
| if (type != 0) { |
| synchronized (EclipseRGraphic.this.userExchangeLock) { |
| EclipseRGraphic.this.locatorNotificationDirectScheduled= false; |
| } |
| } |
| } |
| } |
| }; |
| |
| |
| public EclipseRGraphic(final int devId, final double w, final double h, final InitConfig config, |
| final boolean active, final ToolRClientGraphicActions actions, final int options, |
| final EclipseRGraphicFactory manager) { |
| this.devId= devId; |
| this.isActive= active; |
| this.actions= actions; |
| this.manager= manager; |
| this.options= options; |
| |
| this.display= UIAccess.getDisplay(); |
| this.swtFontManager= manager.getFontManager(this.display); |
| // this.swtFontManager= new FontManager(this.display); // -> dispose! |
| this.swtColorManager= manager.getColorManager(this.display); |
| |
| this.size= this.nextSize= new double[] { w, h }; |
| initPanel(w, h, config); |
| } |
| |
| |
| @Override |
| public String getLabel() { |
| final StringBuilder sb= new StringBuilder(); |
| sb.append("Device "); |
| sb.append(this.devId + 1); |
| if (this.actions != null) { |
| final String rLabel= this.actions.getRLabel(); |
| if (rLabel != null && rLabel.length() > 0) { |
| sb.append(" │ "); |
| sb.append(rLabel); |
| } |
| } |
| final boolean locator= isLocatorStarted(); |
| if (this.isActive || locator) { |
| sb.append(" \t<"); //$NON-NLS-1$ |
| if (this.isActive) { |
| sb.append("active+"); //$NON-NLS-1$ |
| } |
| if (locator) { |
| sb.append("locator+"); //$NON-NLS-1$ |
| } |
| sb.replace(sb.length()-1, sb.length(), ">"); //$NON-NLS-1$ |
| } |
| return sb.toString(); |
| } |
| |
| private void add(final ERGraphicInstruction instr) { |
| // adding is always in R thread |
| if (this.instructionsNew == null) { |
| this.instructionsNew= new ERGraphicInstruction[512]; |
| } |
| else if (this.instructionsNewSize >= this.instructionsNew.length) { |
| final ERGraphicInstruction[] newArray= new ERGraphicInstruction[this.instructionsNewSize + 512]; |
| // System.out.println("NewArray " + this.instructionsNewSize + " -> " + newArray.length); |
| System.arraycopy(this.instructionsNew, 0, newArray, 0, this.instructionsNewSize); |
| this.instructionsNew= newArray; |
| } |
| this.instructionsNew[this.instructionsNewSize]= instr; |
| this.instructionsNewSize++; |
| } |
| |
| protected void initPanel(final double w, final double h, final InitConfig config) { |
| this.drawingStopDelay= 33; |
| this.drawingForceDelay= 333; |
| |
| this.currentFontFamily= null; |
| this.currentFontMapping= null; |
| |
| final IPreferencesService preferences= Platform.getPreferencesService(); |
| this.serifFontName= preferences.getString(RGraphics.FONTS_PREF_QUALIFIER, RGraphics.PREF_FONTS_SERIF_FONTNAME_KEY, "", null); |
| this.sansFontName= preferences.getString(RGraphics.FONTS_PREF_QUALIFIER, RGraphics.PREF_FONTS_SANS_FONTNAME_KEY, "", null); |
| this.monoFontName= preferences.getString(RGraphics.FONTS_PREF_QUALIFIER, RGraphics.PREF_FONTS_MONO_FONTNAME_KEY, "", null); |
| if (preferences.getBoolean(RGraphics.FONTS_PREF_QUALIFIER, RGraphics.PREF_FONTS_SYMBOL_USE_KEY, true, null)) { |
| this.symbolFontName= preferences.getString(RGraphics.FONTS_PREF_QUALIFIER, RGraphics.PREF_FONTS_SYMBOL_FONTNAME_KEY, "Symbol", null); //$NON-NLS-1$ |
| final String encoding= preferences.getString(RGraphics.FONTS_PREF_QUALIFIER, RGraphics.PREF_FONTS_SYMBOL_ENCODING_KEY, "AdobeSymbol", null); //$NON-NLS-1$ |
| if ("AdobeSymbol".equals(encoding)) { //$NON-NLS-1$ |
| this.symbolFontMapping= ADBSYMBOL_MAPPING; |
| } |
| else { |
| this.symbolFontMapping= null; |
| } |
| } |
| else { |
| this.symbolFontName= null; |
| this.symbolFontMapping= null; |
| } |
| this.canvasColor= (config.canvasColor & 0xffffff); |
| |
| add(new GraphicInitialization(w, h, this.canvasColor, this.swtColorManager.getColor(this.canvasColor))); |
| } |
| |
| @Override |
| public void reset(final double w, final double h, final InitConfig config) { |
| synchronized (this.stateLock) { |
| internalReset(); |
| } |
| |
| initPanel(w, h, config); |
| } |
| |
| private void internalReset() { |
| if (this.instructionsNew != null) { |
| if (this.instructionsNewSize > 0) { |
| disposeElements(this.instructionsNew, 0, this.instructionsNewSize); |
| } |
| this.instructionsNew= null; |
| this.instructionsNewSize= 0; |
| } |
| if (this.instructionsUpdate != null) { |
| if (this.instructionsUpdateSize > 0) { |
| disposeElements(this.instructionsUpdate, this.instructionsUpdateStart, this.instructionsUpdateStart+this.instructionsUpdateSize); |
| } |
| this.instructionsUpdate= null; |
| this.instructionsUpdateStart= 0; |
| this.instructionsUpdateSize= 0; |
| } |
| this.drawingStoppedStamp= System.nanoTime(); |
| this.instructionsNotifiedStamp= this.drawingStoppedStamp - 1000 * MILLI_NANOS; |
| } |
| |
| private void execInDisplay(final Runnable runnable) { |
| try { |
| this.display.asyncExec(runnable); |
| } |
| catch (final SWTException e) { |
| if (e.code != SWT.ERROR_DEVICE_DISPOSED) { |
| throw e; |
| } |
| } |
| } |
| |
| @Override |
| public int getDevId() { |
| return this.devId; |
| } |
| |
| @Override |
| public void setActive(final boolean active) { |
| if (this.isActive == active) { |
| return; |
| } |
| synchronized (this.stateLock) { |
| this.isActive= active; |
| if (this.isDisposed) { |
| return; |
| } |
| if (!this.stateNotificationDirectScheduled) { |
| this.stateNotificationDirectScheduled= true; |
| execInDisplay(this.stateNotificationRunnable); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isActive() { |
| return this.isActive; |
| } |
| |
| @Override |
| public void setMode(final int mode) { |
| synchronized (this.stateLock) { |
| if (this.mode == mode) { |
| return; |
| } |
| if (mode != 1) { |
| this.drawingStoppedStamp= System.nanoTime(); |
| flushNewInstructions(); |
| } |
| this.mode= mode; |
| if (this.isDisposed) { |
| return; |
| } |
| if (mode == 1 && this.modeNotified != 1 // need start |
| && !this.stateNotificationDirectScheduled ) { |
| this.stateNotificationDirectScheduled= true; |
| execInDisplay(this.stateNotificationRunnable); |
| } |
| else if (mode != 1 // stop |
| && !(this.stateNotificationDirectScheduled || this.stateNotificationDelayedScheduled) ) { |
| this.stateNotificationDirectScheduled= true; |
| execInDisplay(this.stateNotificationRunnable); |
| } |
| } |
| } |
| |
| private void flushNewInstructions() { |
| if (this.instructionsNewSize > 0) { |
| if (this.instructionsUpdate == null) { |
| this.instructionsUpdate= this.instructionsNew; |
| this.instructionsUpdateStart= 0; |
| this.instructionsUpdateSize= this.instructionsNewSize; |
| this.instructionsNew= null; |
| this.instructionsNewSize= 0; |
| } |
| else { |
| final int newSize= this.instructionsUpdateStart + this.instructionsUpdateSize + this.instructionsNewSize; |
| if (newSize > this.instructionsUpdate.length) { |
| final ERGraphicInstruction[] newArray= new ERGraphicInstruction[newSize + 512]; |
| System.arraycopy(this.instructionsUpdate, 0, newArray, 0, this.instructionsUpdateStart + this.instructionsUpdateSize); |
| this.instructionsUpdate= newArray; |
| } |
| System.arraycopy(this.instructionsNew, 0, this.instructionsUpdate, this.instructionsUpdateStart + this.instructionsUpdateSize, this.instructionsNewSize); |
| this.instructionsUpdateSize+= this.instructionsNewSize; |
| this.instructionsNewSize= 0; |
| } |
| } |
| } |
| |
| private List<ERGraphicInstruction> getCurrentInstructions() { |
| synchronized (this.stateLock) { |
| flushNewInstructions(); |
| return ImCollections.newList(this.instructionsUpdate).subList(0, |
| this.instructionsSize + this.instructionsUpdateSize ); |
| } |
| } |
| |
| @Override |
| public double[] computeSize() { |
| return this.size; |
| } |
| |
| |
| protected void printFont() { |
| System.out.println(this.currentFontFamily.name + " " + this.currentFontStyle + " " + this.currentFontSize); |
| } |
| |
| @Override |
| public double[] computeFontMetric(final int ch) { |
| // System.out.println("==\nTextMetrics: \"" + ((char) ch) + "\" (" + ch + ")"); printFont(); |
| return this.currentFontFamily.getCharMetrics(this.currentFontStyle, this.currentFontSize, |
| (this.currentFontMapping != null) ? this.currentFontMapping.encode(ch) : ch ); |
| } |
| |
| @Override |
| public double[] computeStringWidth(final String txt) { |
| // System.out.println("==\nTextWidth: \"" + txt + "\""); printFont(); |
| return computeStringWidthEnc((this.currentFontMapping != null) ? this.currentFontMapping.encode(txt) : txt); |
| } |
| |
| protected final double[] computeStringWidthEnc(final String text) { |
| if (text.equals(this.lastStringEnc)) { |
| return this.lastStringWidth; |
| } |
| |
| final double[] answer= this.currentFontFamily.getStringWidth(this.currentFontStyle, this.currentFontSize, text); |
| |
| this.lastStringEnc= text; |
| return (this.lastStringWidth= answer); |
| } |
| |
| |
| @Override |
| public void addSetClip(final double x0, final double y0, final double x1, final double y1) { |
| final ClipSetting instr= new ClipSetting(x0, y0, x1, y1); |
| add(instr); |
| } |
| |
| @Override |
| public void addSetColor(final int color) { |
| final ColorSetting instr= new ColorSetting(color, |
| this.swtColorManager.getColor((color & 0xffffff)) ); |
| add(instr); |
| } |
| |
| @Override |
| public void addSetFill(final int color) { |
| final FillSetting instr= new FillSetting(color, |
| this.swtColorManager.getColor((color & 0xffffff)) ); |
| add(instr); |
| } |
| |
| @Override |
| public void addSetFont(String family, final int face, final float pointSize, |
| final float lineHeight) { |
| // System.out.println("==\nSetFont: \"" + family + "\" " + face + " " + pointSize); |
| switch (face) { |
| case 2: |
| case 3: |
| case 4: |
| family= getFontName(family); |
| this.currentFontStyle= face - 1; |
| this.currentFontMapping= null; |
| break; |
| case 5: |
| if (this.symbolFontName != null) { |
| family= this.symbolFontName; |
| this.currentFontStyle= 0; |
| this.currentFontMapping= this.symbolFontMapping; |
| break; |
| } |
| //$FALL-THROUGH$ |
| default: |
| family= getFontName(family); |
| this.currentFontStyle= 0; |
| this.currentFontMapping= null; |
| break; |
| } |
| this.currentFontFamily= this.swtFontManager.getFamily(family); |
| this.currentFontRFace= face; |
| this.currentFontSize= (int) (pointSize + 0.5); |
| |
| this.lastStringEnc= null; |
| |
| final FontSetting instr= new FontSetting(family, face, pointSize, lineHeight, |
| this.currentFontFamily.getSWTFont(this.currentFontStyle, this.currentFontSize), |
| this.currentFontFamily.getSWTFontProperties(this.currentFontStyle, this.currentFontSize) ); |
| add(instr); |
| } |
| |
| private String getFontName(final String family) { |
| if (family.length() == 0 || family.equals("sansserif")) { |
| return this.sansFontName; |
| } |
| else if (family.equals("serif")) { |
| return this.serifFontName; |
| } |
| else if (family.equals("mono")) { |
| return this.monoFontName; |
| } |
| else { |
| return family; |
| } |
| } |
| |
| @Override |
| public void addSetLine(final int type, final float width, |
| final byte cap, final byte join, final float joinMiterLimit) { |
| final LineSetting instr= new LineSetting(type, width, cap, join, joinMiterLimit); |
| add(instr); |
| } |
| |
| @Override |
| public void addDrawLine(final double x0, final double y0, final double x1, final double y1) { |
| final LineElement instr= new LineElement(x0, y0, x1, y1); |
| add(instr); |
| } |
| |
| @Override |
| public void addDrawRect(final double x0, final double y0, final double x1, final double y1) { |
| final RectElement instr= new RectElement(x0, y0, x1, y1); |
| add(instr); |
| } |
| |
| @Override |
| public void addDrawPolyline(final double[] x, final double[] y) { |
| final PolylineElement instr= new PolylineElement(x, y); |
| add(instr); |
| } |
| |
| @Override |
| public void addDrawPolygon(final double[] x, final double[] y) { |
| final PolygonElement instr= new PolygonElement(x, y); |
| add(instr); |
| } |
| |
| @Override |
| public void addDrawPath(final int[] n, final double[] x, final double[] y, final int winding) { |
| final Path path= new Path(this.display); |
| int k= 0, end= 0; |
| for (int i= 0; i < n.length; i++) { |
| end+= n[i]; |
| path.moveTo((float) Math.floor(x[k] + 0.5), (float) Math.floor(y[k++] + 0.5)); |
| while (k < end) { |
| path.lineTo((float) Math.floor(x[k] + 0.5), (float) Math.floor(y[k++] + 0.5)); |
| } |
| path.close(); |
| } |
| final PathElement instr= new PathElement(n, x, y, winding, path); |
| add(instr); |
| } |
| |
| @Override |
| public void addDrawCircle(final double x, final double y, final double r) { |
| final CircleElement instr= new CircleElement(x, y, r); |
| add(instr); |
| } |
| |
| @Override |
| public void addDrawText(final String txt, |
| final double x, final double y, final double rDeg, final double hAdj) { |
| // System.out.println("==\nDrawText: " + x + ", " + y + " " + hAdj + " \"" + txt + "\""); printFont(); |
| final String text= (this.currentFontMapping != null) ? this.currentFontMapping.encode(txt) : txt; |
| final TextElement instr= new TextElement(text, x, y, rDeg, hAdj, |
| (hAdj != 0) ? computeStringWidthEnc(text)[0] : 0); |
| add(instr); |
| } |
| |
| @Override |
| public void addDrawRaster(final byte[] imgData, final boolean hasAlpha, |
| final int imgWidth, final int imgHeight, |
| final double x, final double y, final double w, final double h, |
| final double rDeg, final boolean interpolate) { |
| final ImageData imageData= new ImageData(imgWidth, imgHeight, 32, DIRECT_PALETTE, 4, imgData); |
| if (hasAlpha) { |
| final byte[] alpha= new byte[imgWidth*imgHeight]; |
| for (int i= 0; i < alpha.length; i++) { |
| alpha[i]= imgData[i*4 + 3]; |
| } |
| imageData.alphaData= alpha; |
| } |
| final Image swtImage= new Image(this.display, imageData); |
| final RasterElement instr= new RasterElement(imgData, imgWidth, imgHeight, x, y, w, h, |
| rDeg, interpolate, swtImage ); |
| add(instr); |
| } |
| |
| @Override |
| public byte[] capture(final int width, final int height) { |
| ImageData imageData; |
| { Image image= null; |
| GC gc= null; |
| try { |
| image= new Image(this.display, width, height); |
| gc= new GC(image); |
| final DefaultGCRenderer renderer= new DefaultGCRenderer(); |
| final List<ERGraphicInstruction> instructions= getCurrentInstructions(); |
| if (instructions.isEmpty()) { |
| return null; |
| } |
| { final RGraphicInitialization init= (RGraphicInitialization) instructions.get(0); |
| double scale; |
| if (width == (int) (init.width + 0.5)) { |
| scale= 1.0; |
| } |
| else { |
| scale= width / init.width; |
| } |
| renderer.clear(scale); |
| } |
| renderer.paint(gc, instructions); |
| gc.dispose(); |
| gc= null; |
| imageData= image.getImageData(); |
| image.dispose(); |
| image= null; |
| } |
| finally { |
| if (gc != null && !gc.isDisposed()) { |
| gc.dispose(); |
| } |
| if (image != null && !image.isDisposed()) { |
| image.dispose(); |
| } |
| } |
| } |
| if (imageData == null || !imageData.palette.isDirect) { |
| return null; |
| } |
| if (imageData.palette.redMask != DIRECT_RED_MASK |
| || imageData.palette.greenMask != DIRECT_GREEN_MASK |
| || imageData.palette.blueMask != DIRECT_BLUE_MASK |
| || imageData.scanlinePad != 4 |
| || imageData.bytesPerLine != width * 4 |
| || imageData.data.length != width * height * 4 ) { |
| final byte[] data= (imageData.data.length == width * height * 4) ? |
| imageData.data : new byte[width * height * 4]; |
| final int blueMask= imageData.palette.blueMask; |
| final int blueShift= imageData.palette.blueShift; |
| final int greenMask= imageData.palette.greenMask; |
| final int greenShift= imageData.palette.greenShift; |
| final int redMask= imageData.palette.redMask; |
| final int redShift= imageData.palette.redShift; |
| int i= 0; |
| for (int y= 0; y < height; y++) { |
| for (int x= 0; x < width; x++) { |
| final int p= imageData.getPixel(x, y); |
| data[i++]= (blueShift < 0) ? |
| (byte) ((p & blueMask) >>> -blueShift) : |
| (byte) ((p & blueMask) << blueShift); |
| data[i++]= (greenShift < 0) ? |
| (byte) ((p & greenMask) >>> -greenShift) : |
| (byte) ((p & greenMask) << greenShift); |
| data[i++]= (redShift < 0) ? |
| (byte) ((p & redMask) >>> -redShift) : |
| (byte) ((p & redMask) << redShift); |
| data[i++]= (byte) 255; |
| } |
| } |
| return data; |
| } |
| return imageData.data; |
| } |
| |
| |
| protected void waitRUserExchange(final String type, |
| final RService r, final ProgressMonitor m, |
| final Callable<Boolean> cancelListener) { |
| final RServiceControlExtension rControl= (r instanceof RServiceControlExtension) ? |
| (RServiceControlExtension) r : null; |
| if (rControl != null && cancelListener != null) { |
| rControl.addCancelHandler(cancelListener); |
| rControl.getWaitLock().lock(); |
| } |
| try { |
| while (true) { |
| synchronized (this.userExchangeLock) { |
| if (this.userExchangeRType != type) { |
| this.userExchangeRCallback= null; |
| return; |
| } |
| if (this.isLocalClosed || this.isRClosed || m.isCanceled() ) { |
| this.userExchangeRType= null; |
| this.userExchangeRCallback= null; |
| return; |
| } |
| this.userExchangeRCallback= rControl; |
| } |
| |
| if (rControl != null) { |
| rControl.waitingForUser(m); |
| } |
| else { |
| try { |
| Thread.sleep(50); |
| } |
| catch (final InterruptedException e) { |
| } |
| } |
| } |
| } |
| finally { |
| if (rControl != null && cancelListener != null) { |
| rControl.getWaitLock().unlock(); |
| rControl.removeCancelHandler(cancelListener); |
| } |
| } |
| } |
| |
| |
| private void internalStartLocator(final LocatorCallback callback) { |
| this.locatorCallback= callback; |
| this.locatorMessage= new InfoStatus(RGraphics.BUNDLE_ID, callback.getMessage()); |
| this.locatorStopTypes= callback.getStopTypes(); |
| this.locatorDeferredStamp= Long.MIN_VALUE; |
| |
| if (this.display.isDisposed()) { |
| return; |
| } |
| if (!this.locatorNotificationDirectScheduled) { |
| execInDisplay(this.locatorNotificationRunnable); |
| } |
| } |
| |
| private void internalStopLocator(final boolean deferred) { |
| if (deferred) { |
| this.locatorDeferredStamp= System.nanoTime() + 500 * MILLI_NANOS; |
| if (this.display.isDisposed()) { |
| return; |
| } |
| if (!(this.locatorNotificationDirectScheduled || this.locatorNotificationDeferredScheduled)) { |
| this.locatorNotificationDirectScheduled= true; |
| execInDisplay(this.locatorNotificationRunnable); |
| } |
| return; |
| } |
| |
| this.locatorCallback= null; |
| this.locatorMessage= null; |
| this.locatorStopTypes= Collections.emptySet(); |
| this.locatorDeferredStamp= Long.MIN_VALUE; |
| |
| if (this.display.isDisposed()) { |
| return; |
| } |
| if (!this.locatorNotificationDirectScheduled) { |
| this.locatorNotificationDirectScheduled= true; |
| execInDisplay(this.locatorNotificationRunnable); |
| } |
| } |
| |
| @Override |
| public double[] runRLocator(final RService r, final ProgressMonitor m) { |
| synchronized (this.userExchangeLock) { |
| if (this.locatorCallback != null && this.locatorCallback != R_LOCATOR_CALLBACK) { |
| return null; |
| } |
| this.userExchangeRType= "locator"; |
| internalStartLocator(R_LOCATOR_CALLBACK); |
| |
| this.locatorLocationValue= null; |
| } |
| waitRUserExchange("locator", r, m, new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| return Boolean.valueOf(answerLocator(null, null, true)); |
| } |
| }); |
| final double[] value; |
| synchronized (this.userExchangeLock) { |
| value= this.locatorLocationValue; |
| if (this.userExchangeRType == "locator") { |
| this.userExchangeRType= null; |
| } |
| // avoid flickering as well as stale locators |
| internalStopLocator(value != null); |
| } |
| return value; |
| } |
| |
| @Override |
| public Status startLocalLocator(final LocatorCallback callback) { |
| if (callback == null) { |
| throw new NullPointerException("callback"); //$NON-NLS-1$ |
| } |
| synchronized (this.userExchangeLock) { |
| if (this.locatorCallback != null && this.locatorCallback != callback) { |
| return new ErrorStatus(RGraphics.BUNDLE_ID, "Another locator is already started."); |
| } |
| internalStartLocator(callback); |
| } |
| return Statuses.OK_STATUS; |
| } |
| |
| @Override |
| public boolean isLocatorStarted() { |
| return (this.locatorCallback != null); |
| } |
| |
| @Override |
| public Collection<String> getLocatorStopTypes() { |
| return this.locatorStopTypes; |
| } |
| |
| @Override |
| public void returnLocator(final double x, final double y) { |
| answerLocator(null, new double[] { x, y }, false); |
| } |
| |
| @Override |
| public void stopLocator(final String type) { |
| answerLocator(type, null, false); |
| } |
| |
| private boolean answerLocator(final String type, final double[] xy, |
| final boolean onlyR) { |
| synchronized (this.locatorAnswerLock) { |
| RServiceControlExtension rControl= null; |
| LocatorCallback callback; |
| synchronized (this.userExchangeLock) { |
| if (this.locatorCallback == null || this.locatorDeferredStamp != Long.MIN_VALUE) { |
| return false; |
| } |
| if (this.locatorCallback == R_LOCATOR_CALLBACK) { |
| if (this.userExchangeRType == "locator") { //$NON-NLS-1$ |
| this.userExchangeRType= null; |
| rControl= this.userExchangeRCallback; |
| } |
| else { |
| return false; |
| } |
| } |
| else if (onlyR) { |
| return false; |
| } |
| if (xy == null && type != null && !this.locatorStopTypes.contains(type)) { |
| return false; |
| } |
| this.locatorLocationValue= xy; |
| callback= this.locatorCallback; |
| } |
| |
| final int code; |
| if (callback == R_LOCATOR_CALLBACK) { |
| if (xy != null) { |
| code= -1; |
| } |
| else { |
| code= LocatorCallback.STOP; |
| } |
| if (rControl != null) { |
| rControl.getWaitLock().lock(); |
| try { |
| rControl.resume(); |
| } |
| finally { |
| rControl.getWaitLock().unlock(); |
| } |
| } |
| } |
| else { |
| if (xy != null) { |
| code= callback.located(xy[0], xy[1]); |
| } |
| else { |
| code= LocatorCallback.STOP; |
| callback.stopped(type); |
| } |
| } |
| synchronized (this.userExchangeLock) { |
| if (code == LocatorCallback.NEXT) { |
| internalStartLocator(callback); |
| } |
| else { |
| internalStopLocator((code == -1)); |
| } |
| } |
| return true; |
| } |
| } |
| |
| |
| public void closeFromR() { |
| this.isRClosed= true; |
| if (this.isLocalClosed |
| || (this.options & RClientGraphicFactory.R_CLOSE_OFF) == 0) { |
| dispose(); |
| } |
| answerLocator(null, null, true); |
| setActive(false); |
| } |
| |
| protected void dispose() { |
| this.graphicListeners.clear(); |
| Runnable runnable= null; |
| synchronized (this.stateLock) { |
| if (!this.isDisposed) { |
| this.isDisposed= true; |
| internalReset(); |
| synchronized (this.instructionsLock) { |
| if (this.instructionsSize > 0) { |
| runnable= new DisposeRunnable(this.instructions, this.instructionsSize); |
| } |
| this.instructionsSize= 0; |
| this.instructions= null; |
| } |
| } |
| } |
| if (runnable != null) { |
| execInDisplay(runnable); |
| } |
| } |
| |
| |
| @Override |
| public List<ERGraphicInstruction> getInstructions() { |
| synchronized (this.instructionsLock) { |
| return (this.instructionsSize > 0) ? |
| ImCollections.newList(this.instructions).subList(0, this.instructionsSize) : |
| ImCollections.<ERGraphicInstruction>emptyList(); |
| } |
| } |
| |
| @Override |
| public @Nullable RTool getRHandle() { |
| if (this.actions != null) { |
| return this.actions.getRHandle(); |
| } |
| return null; |
| } |
| |
| @Override |
| public Status resize(final double w, final double h) { |
| if (this.actions != null) { |
| this.nextSize= new double[] { w, h }; |
| return this.actions.resizeGraphic(this.devId, new Runnable() { |
| @Override |
| public void run() { |
| EclipseRGraphic.this.size= EclipseRGraphic.this.nextSize; |
| } |
| }); |
| } |
| return null; |
| } |
| |
| @Override |
| public Status close() { |
| if (this.isRClosed) { |
| this.isLocalClosed= true; |
| dispose(); |
| } |
| if (this.actions != null) { |
| answerLocator(null, null, false); |
| return this.actions.closeGraphic(this.devId); |
| } |
| else { |
| this.isLocalClosed= true; |
| answerLocator(null, null, false); |
| this.manager.close(this); |
| dispose(); |
| } |
| return null; |
| } |
| |
| @Override |
| public void addListener(final Listener listener) { |
| this.graphicListeners.add(listener); |
| } |
| |
| @Override |
| public void removeListener(final Listener listener) { |
| this.graphicListeners.remove(listener); |
| } |
| |
| protected void updateMessage() { |
| Status message; |
| if (this.locatorMessage != null) { |
| message= this.locatorMessage; |
| } |
| else { |
| message= Statuses.OK_STATUS; |
| } |
| if (!this.message.equals(message)) { |
| this.message= message; |
| for (final StatusChangeListener listener : this.messageListeners.toList()) { |
| listener.onStatusChanged(message); |
| } |
| } |
| } |
| |
| @Override |
| public Status getMessage() { |
| return this.message; |
| } |
| |
| @Override |
| public void addMessageListener(final StatusChangeListener listener) { |
| this.messageListeners.add(listener); |
| } |
| |
| @Override |
| public void removeMessageListener(final StatusChangeListener listener) { |
| this.messageListeners.remove(listener); |
| } |
| |
| |
| protected void preAction() throws StatusException { |
| if (this.actions == null || this.actions.getRHandle() == null) { |
| throw new UnsupportedOperationException(); |
| } |
| if (this.isRClosed) { |
| throw new StatusException(new ErrorStatus(RGraphics.BUNDLE_ID, |
| "The R graphic device is already closed." )); |
| } |
| } |
| |
| @Override |
| public void copy(final String toDev, final String toDevFile, final String toDevArgs, |
| final ProgressMonitor m) throws StatusException { |
| preAction(); |
| this.actions.copy(this.devId, toDev, toDevFile, toDevArgs, m); |
| } |
| |
| @Override |
| public double[] convertGraphic2User(final double[] xy, |
| final ProgressMonitor m) throws StatusException { |
| preAction(); |
| return this.actions.convertGraphic2User(this.devId, xy, m); |
| } |
| |
| @Override |
| public double[] convertUser2Graphic(final double[] xy, |
| final ProgressMonitor m) throws StatusException { |
| preAction(); |
| return this.actions.convertUser2Graphic(this.devId, xy, m); |
| } |
| |
| } |