blob: 8bd78e4f1e48cca025dc32fd02b73eafb79c99c8 [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.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.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= Status.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.isEmpty() || 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 Status.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= Status.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);
}
}