SwtGraphicContext.java

package de.slothsoft.charts.swt;

import java.util.Objects;
import java.util.function.Consumer;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Transform;

import de.slothsoft.charts.Area;
import de.slothsoft.charts.Font;
import de.slothsoft.charts.GraphicContext;

/**
 * A {@link GraphicContext} implementation using the SWT {@link GC} to draw on whatever
 * you like.
 *
 * @author Stef Schulz
 * @since 0.1.0
 */

public class SwtGraphicContext implements GraphicContext {

	private GC delegate;

	private int colorAsInt;
	Color color;
	private Font fontAsEnum;
	org.eclipse.swt.graphics.Font font;

	Transform transform;
	private double scaleX = 1;
	private double scaleY = 1;

	/**
	 * Constructor.
	 *
	 * @param delegate the GC to draw on; cannot be null
	 */

	public SwtGraphicContext(GC delegate) {
		this.delegate = Objects.requireNonNull(delegate);
	}

	@Override
	public void setColor(int colorAsInt) {
		if (this.color != null) {
			this.color.dispose();
		}

		final int alpha = (colorAsInt >> 24) & 0xFF;
		final int red = (colorAsInt >> 16) & 0xFF;
		final int green = (colorAsInt >> 8) & 0xFF;
		final int blue = colorAsInt & 0xFF;
		this.color = new Color(this.delegate.getDevice(), new RGB(red, green, blue));
		this.delegate.setBackground(this.color);
		this.delegate.setForeground(this.color);
		this.delegate.setAlpha(alpha);
		this.colorAsInt = colorAsInt;
	}

	@Override
	public int getColor() {
		return this.colorAsInt;
	}

	@Override
	public void setFont(Font fontAsEnum) {
		final org.eclipse.swt.graphics.Font newFont = createFont(fontAsEnum);
		if (this.font != null) {
			this.font.dispose();
		}
		this.font = newFont;
		this.delegate.setFont(this.font);
		this.fontAsEnum = fontAsEnum;
	}

	private org.eclipse.swt.graphics.Font createFont(Font enumFont) {
		final FontData[] fontDatas = this.delegate.getFont().getFontData();
		for (int i = 0; i < fontDatas.length; i++) {
			fontDatas[i].setHeight(enumFont.getSize());
			fontDatas[i].setStyle(createFontStyle(enumFont));

		}
		return new org.eclipse.swt.graphics.Font(this.delegate.getDevice(), fontDatas);
	}

	private static int createFontStyle(Font enumFont) {
		int result = SWT.NORMAL;
		if (enumFont.isBold()) {
			result |= SWT.BOLD;
		}
		return result;
	}

	@Override
	public Font getFont() {
		return this.fontAsEnum;
	}

	@Override
	public Area calculateTextSize(String text) {
		final Point result = this.delegate.textExtent(text);
		return new Area(result.x, result.y);
	}

	@Override
	public void translate(double x, double y) {
		doToTransform(t -> t.translate((int) x, (int) y));
	}

	private void doToTransform(Consumer<Transform> transformConsumer) {
		final Transform usedTransform = fetchTransform();
		this.delegate.getTransform(usedTransform);
		transformConsumer.accept(usedTransform);
		this.delegate.setTransform(usedTransform);
	}

	Transform fetchTransform() {
		if (this.transform == null) {
			this.transform = new Transform(this.delegate.getDevice());
		}
		return this.transform;
	}

	@Override
	public void scale(double x, double y) {
		this.scaleX *= x;
		this.scaleY *= y;
	}

	@Override
	public void clip(Area rect) {
		if (rect == null) {
			this.delegate.setClipping((Rectangle) null);
		} else {
			this.delegate.setClipping(convertToSwtX(rect.getStartX()), convertToSwtY(rect.getStartY()),
					convertToSwtX(rect.calculateWidth()), convertToSwtY(rect.calculateHeight()));
		}
	}

	@Override
	public void drawLine(double x1, double y1, double x2, double y2) {
		this.delegate.drawLine(convertToSwtX(x1), convertToSwtY(y1), convertToSwtX(x2), convertToSwtX(y2));
	}

	@Override
	public void fillRectangle(double x, double y, double width, double height) {
		this.delegate.fillRectangle(convertToSwtX(x), convertToSwtY(y), convertToSwtX(width), convertToSwtY(height));
	}

	@Override
	public void drawPolyline(double[] x, double[] y) {
		this.delegate.drawPolyline(convertToSwtPoints(x, y));
	}

	private int[] convertToSwtPoints(double[] x, double[] y) {
		final int[] points = new int[x.length + y.length];
		for (int i = 0; i < x.length; i++) {
			points[2 * i] = convertToSwtX(x[i]);
			points[2 * i + 1] = convertToSwtY(y[i]);
		}
		return points;
	}

	private int convertToSwtX(double x) {
		return (int) (this.scaleX * x);
	}

	private int convertToSwtY(double Y) {
		return (int) (this.scaleY * Y);
	}

	@Override
	public void fillPolygon(double[] x, double[] y) {
		this.delegate.fillPolygon(convertToSwtPoints(x, y));
	}

	@Override
	public void drawText(double x, double y, String text) {
		this.delegate.drawText(text, convertToSwtX(x), convertToSwtY(y), true);
	}

	@Override
	public void fillOval(double x, double y, double width, double height) {
		this.delegate.fillOval(convertToSwtX(x), convertToSwtY(y), convertToSwtX(width), convertToSwtY(height));
	}

	@Override
	public void fillArc(double x, double y, double width, double height, double startAngle, double arcAngle) {
		this.delegate.fillArc(convertToSwtX(x), convertToSwtY(y), convertToSwtX(width), convertToSwtY(height),
				(int) startAngle, (int) arcAngle);
	}

	/**
	 * Disposes of the operating system resources associated with this resource.
	 * Applications must dispose of all resources which they allocate. This method does
	 * nothing if the resource is already disposed.
	 */

	public void dispose() {
		if (this.transform != null) {
			this.transform.dispose();
			this.transform = null;
		}
		if (this.font != null) {
			this.font.dispose();
			this.font = null;
		}
		if (this.color != null) {
			this.color.dispose();
			this.color = null;
		}
	}

	/**
	 * Returns the GC.
	 *
	 * @return the GC to draw on; never null
	 */

	public GC getDelegate() {
		return this.delegate;
	}

	/**
	 * Sets the GC.
	 *
	 * @param newDelegate the GC to draw on; cannot be null
	 * @return this instance
	 */

	public SwtGraphicContext delegate(GC newDelegate) {
		setDelegate(newDelegate);
		return this;
	}

	/**
	 * Sets the GC.
	 *
	 * @param delegate the GC to draw on; cannot be null
	 */

	public void setDelegate(GC delegate) {
		this.delegate = Objects.requireNonNull(delegate);
	}

}