/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.]
 *
 * ------------------
 * SWTGraphics2D.java
 * ------------------
 * (C) Copyright 2006-2013, by Henry Proudhon and Contributors.
 *
 * Original Author:  Henry Proudhon (henry.proudhon AT ensmp.fr);
 * Contributor(s):   Cedric Chabanois (cchabanois AT no-log.org);
 *                   David Gilbert (for Object Refinery Limited);
 *                   Ronnie Duan (see bug report 2583891);
 *                   Kevin Xu (parts of patch 3506228);
 *
 * Changes
 * -------
 * 14-Jun-2006 : New class (HP);
 * 29-Jan-2007 : Fixed the fillRect method (HP);
 * 31-Jan-2007 : Moved the dummy JPanel to SWTUtils.java,
 *               implemented the drawLine method (HP);
 * 07-Apr-2007 : Dispose some of the swt ressources,
 *               thanks to silent for pointing this out (HP);
 * 23-May-2007 : Removed resource leaks by adding a resource pool (CC);
 * 15-Jun-2007 : Fixed compile error for JDK 1.4 (DG);
 * 22-Oct-2007 : Implemented clipping (HP);
 * 22-Oct-2007 : Implemented some AlphaComposite support (HP);
 * 23-Oct-2007 : Added mechanism for storing RenderingHints (which are
 *               still ignored at this point) (DG);
 * 23-Oct-2007 : Implemented drawPolygon(), drawPolyline(), drawOval(),
 *               fillOval(), drawArc() and fillArc() (DG);
 * 27-Nov-2007 : Implemented a couple of drawImage() methods (DG);
 * 18-Nov-2008 : Check for GradientPaint in setPaint() method (DG);
 * 27-Feb-2009 : Implemented fillPolygon() - see bug 2583891 (DG);
 * 04-Jul-2012 : Fixed get/setStroke() - see bug 3514487 (DG);
 * 18-Sep-2012 : Fixed missing text - see bug 3482106 and patch 3506228 (DG);
 * 03-Jul-2013 : Use ParamChecks (DG);
 *
 */

package org.jfree.experimental.swt;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Resource;
import org.eclipse.swt.graphics.Transform;
import org.jfree.chart.util.ParamChecks;

/**
 * This is a class utility to draw Graphics2D stuff on a swt composite.
 * It is presently developed to use JFreeChart with the Standard
 * Widget Toolkit but may be of a wider use later.
 */
public class SWTGraphics2D extends Graphics2D {

    /** The swt graphic composite */
    private GC gc;

    /**
     * The rendering hints.  For now, these are not used, but at least the
     * basic mechanism is present.
     */
    private RenderingHints hints;

    /** A reference to the compositing rule to apply. This is necessary
     * due to the poor compositing interface of the SWT toolkit. */
    private java.awt.Composite composite;

    /** A HashMap to store the Swt color resources. */
    private Map colorsPool = new HashMap();

    /** A HashMap to store the Swt font resources. */
    private Map fontsPool = new HashMap();

    /** A HashMap to store the Swt transform resources. */
    private Map transformsPool = new HashMap();

    /** A List to store the Swt resources. */
    private List resourcePool = new ArrayList();

    /**
     * Creates a new instance.
     *
     * @param gc  the graphics context.
     */
    public SWTGraphics2D(GC gc) {
        super();
        this.gc = gc;
        this.hints = new RenderingHints(null);
        this.composite = AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f);
        setStroke(new BasicStroke());
    }

    /**
     * Not implemented yet - see {@link Graphics#create()}.
     *
     * @return <code>null</code>.
     */
    public Graphics create() {
        // TODO Auto-generated method stub
        return null;
    }

    /**
     * Not implemented yet - see {@link Graphics2D#getDeviceConfiguration()}.
     *
     * @return <code>null</code>.
     */
    public GraphicsConfiguration getDeviceConfiguration() {
        // TODO Auto-generated method stub
        return null;
    }

    /**
     * Returns the current value for the specified hint key, or
     * <code>null</code> if no value is set.
     *
     * @param hintKey  the hint key (<code>null</code> permitted).
     *
     * @return The hint value, or <code>null</code>.
     *
     * @see #setRenderingHint(RenderingHints.Key, Object)
     */
    public Object getRenderingHint(Key hintKey) {
        return this.hints.get(hintKey);
    }

    /**
     * Sets the value for a rendering hint.  For now, this graphics context
     * ignores all hints.
     *
     * @param hintKey  the key (<code>null</code> not permitted).
     * @param hintValue  the value (must be compatible with the specified key).
     *
     * @throws IllegalArgumentException if <code>hintValue</code> is not
     *         compatible with the <code>hintKey</code>.
     *
     * @see #getRenderingHint(RenderingHints.Key)
     */
    public void setRenderingHint(Key hintKey, Object hintValue) {
        this.hints.put(hintKey, hintValue);
    }

    /**
     * Returns a copy of the hints collection for this graphics context.
     *
     * @return A copy of the hints collection.
     */
    public RenderingHints getRenderingHints() {
        return (RenderingHints) this.hints.clone();
    }

    /**
     * Adds the hints in the specified map to the graphics context, replacing
     * any existing hints.  For now, this graphics context ignores all hints.
     *
     * @param hints  the hints (<code>null</code> not permitted).
     *
     * @see #setRenderingHints(Map)
     */
    public void addRenderingHints(Map hints) {
        this.hints.putAll(hints);
    }

    /**
     * Replaces the existing hints with those contained in the specified
     * map.  Note that, for now, this graphics context ignores all hints.
     *
     * @param hints  the hints (<code>null</code> not permitted).
     *
     * @see #addRenderingHints(Map)
     */
    public void setRenderingHints(Map hints) {
        if (hints == null) {
            throw new NullPointerException("Null 'hints' argument.");
        }
        this.hints = new RenderingHints(hints);
    }

    /**
     * Returns the current paint for this graphics context.
     *
     * @return The current paint.
     *
     * @see #setPaint(Paint)
     */
    public Paint getPaint() {
        // TODO: it might be a good idea to keep a reference to the color
        // specified in setPaint() or setColor(), rather than creating a
        // new object every time getPaint() is called.
        return SWTUtils.toAwtColor(this.gc.getForeground());
    }

    /**
     * Sets the paint for this graphics context.  For now, this graphics
     * context only supports instances of {@link Color} or
     * {@link GradientPaint} (in the latter case there is no real gradient
     * support, the paint used is the <code>Color</code> returned by
     * <code>getColor1()</code>).
     *
     * @param paint  the paint (<code>null</code> permitted, ignored).
     *
     * @see #getPaint()
     * @see #setColor(Color)
     */
    public void setPaint(Paint paint) {
        if (paint == null) {
            return;  // to be consistent with other Graphics2D implementations
        }
        if (paint instanceof Color) {
            setColor((Color) paint);
        }
        else if (paint instanceof GradientPaint) {
            GradientPaint gp = (GradientPaint) paint;
            setColor(gp.getColor1());
        }
        else {
            throw new RuntimeException("Can only handle 'Color' at present.");
        }
    }

    /**
     * Returns the current color for this graphics context.
     *
     * @return The current color.
     *
     * @see #setColor(Color)
     */
    public Color getColor() {
        // TODO: it might be a good idea to keep a reference to the color
        // specified in setPaint() or setColor(), rather than creating a
        // new object every time getPaint() is called.
        return SWTUtils.toAwtColor(this.gc.getForeground());
    }

    /**
     * Sets the current color for this graphics context.
     *
     * @param color  the color (<code>null</code> permitted but ignored).
     *
     * @see #getColor()
     */
    public void setColor(Color color) {
        if (color == null) {
            return;
        }
        org.eclipse.swt.graphics.Color swtColor = getSwtColorFromPool(color);
        this.gc.setForeground(swtColor);
        // handle transparency and compositing.
        if (this.composite instanceof AlphaComposite) {
            AlphaComposite acomp = (AlphaComposite) this.composite;
            switch (acomp.getRule()) {
            case AlphaComposite.SRC_OVER:
                this.gc.setAlpha((int) (color.getAlpha() * acomp.getAlpha()));
                break;
            default:
                this.gc.setAlpha(color.getAlpha());
                break;
            }
        }
    }

    private Color backgroundColor;
    
    /**
     * Sets the background colour.
     *
     * @param color  the colour.
     */
    public void setBackground(Color color) {
        // since this is only used by clearRect(), we don't update the GC yet
        this.backgroundColor = color;
    }

    /**
     * Returns the background colour.
     *
     * @return The background colour (possibly <code>null</code>)..
     */
    public Color getBackground() {
        return this.backgroundColor;
    }

    /**
     * Not implemented - see {@link Graphics#setPaintMode()}.
     */
    public void setPaintMode() {
        // TODO Auto-generated method stub
    }

    /**
     * Not implemented - see {@link Graphics#setXORMode(Color)}.
     *
     * @param color  the colour.
     */
    public void setXORMode(Color color) {
        // TODO Auto-generated method stub
    }

    /**
     * Returns the current composite.
     *
     * @return The current composite.
     *
     * @see #setComposite(Composite)
     */
    public Composite getComposite() {
        return this.composite;
    }

    /**
     * Sets the current composite.  This implementation currently supports
     * only the {@link AlphaComposite} class.
     *
     * @param comp  the composite (<code>null</code> not permitted).
     */
    public void setComposite(Composite comp) {
        if (comp == null) {
            throw new IllegalArgumentException("Null 'comp' argument.");
        }
        this.composite = comp;
        if (comp instanceof AlphaComposite) {
            AlphaComposite acomp = (AlphaComposite) comp;
            int alpha = (int) (acomp.getAlpha() * 0xFF);
            this.gc.setAlpha(alpha);
        }
    }

    /**
     * Returns the current stroke for this graphics context.
     *
     * @return The current stroke.
     *
     * @see #setStroke(Stroke)
     */
    public Stroke getStroke() {
        return new BasicStroke(this.gc.getLineWidth(),
                toAwtLineCap(this.gc.getLineCap()),
                toAwtLineJoin(this.gc.getLineJoin()));
    }

    /**
     * Sets the stroke for this graphics context.  For now, this implementation
     * only recognises the {@link BasicStroke} class.
     *
     * @param stroke  the stroke (<code>null</code> not permitted).
     *
     * @see #getStroke()
     */
    public void setStroke(Stroke stroke) {
        if (stroke == null) {
            throw new IllegalArgumentException("Null 'stroke' argument.");
        }
        if (stroke instanceof BasicStroke) {
            BasicStroke bs = (BasicStroke) stroke;
            this.gc.setLineWidth((int) bs.getLineWidth());
            this.gc.setLineJoin(toSwtLineJoin(bs.getLineJoin()));
            this.gc.setLineCap(toSwtLineCap(bs.getEndCap()));

            // set the line style to solid by default
            this.gc.setLineStyle(SWT.LINE_SOLID);

            // apply dash style if any
            float[] dashes = bs.getDashArray();
            if (dashes != null) {
                int[] swtDashes = new int[dashes.length];
                for (int i = 0; i < swtDashes.length; i++) {
                    swtDashes[i] = (int) dashes[i];
                }
                this.gc.setLineDash(swtDashes);
            }
        }
        else {
            throw new RuntimeException(
                    "Can only handle 'Basic Stroke' at present.");
        }
    }

    /**
     * Applies the specified clip.
     *
     * @param s  the shape for the clip.
     */
    public void clip(Shape s) {
        Path path = toSwtPath(s);
        this.gc.setClipping(path);
        path.dispose();
    }

    /**
     * Returns the clip bounds.
     *
     * @return The clip bounds.
     */
    public Rectangle getClipBounds() {
        org.eclipse.swt.graphics.Rectangle clip = this.gc.getClipping();
        return new Rectangle(clip.x, clip.y, clip.width, clip.height);
    }

    /**
     * Sets the clipping to the intersection of the current clip region and
     * the specified rectangle.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the width.
     * @param height  the height.
     */
    public void clipRect(int x, int y, int width, int height) {
        org.eclipse.swt.graphics.Rectangle clip = this.gc.getClipping();
        org.eclipse.swt.graphics.Rectangle r 
                = new org.eclipse.swt.graphics.Rectangle(x, y, width, height);
        clip.intersect(r);
        this.gc.setClipping(clip);
    }

    /**
     * Returns the current clip.
     *
     * @return The current clip.
     */
    public Shape getClip() {
        return SWTUtils.toAwtRectangle(this.gc.getClipping());
    }

    /**
     * Sets the clip region.
     *
     * @param clip  the clip.
     */
    public void setClip(Shape clip) {
        if (clip == null) {
            return;
        }
        Path clipPath = toSwtPath(clip);
        this.gc.setClipping(clipPath);
        clipPath.dispose();
    }

    /**
     * Sets the clip region to the specified rectangle.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the width.
     * @param height  the height.
     */
    public void setClip(int x, int y, int width, int height) {
        this.gc.setClipping(x, y, width, height);
    }

    /**
     * Returns the current transform.
     *
     * @return The current transform.
     */
    public AffineTransform getTransform() {
        Transform swtTransform = new Transform(this.gc.getDevice());
        this.gc.getTransform(swtTransform);
        AffineTransform awtTransform = toAwtTransform(swtTransform);
        swtTransform.dispose();
        return awtTransform;
    }

    /**
     * Sets the current transform.
     *
     * @param t  the transform.
     */
    public void setTransform(AffineTransform t) {
        Transform transform = getSwtTransformFromPool(t);
        this.gc.setTransform(transform);
    }

    /**
     * Concatenates the specified transform to the existing transform.
     *
     * @param t  the transform.
     */
    public void transform(AffineTransform t) {
        Transform swtTransform = new Transform(this.gc.getDevice());
        this.gc.getTransform(swtTransform);
        swtTransform.multiply(getSwtTransformFromPool(t));
        this.gc.setTransform(swtTransform);
        swtTransform.dispose();
    }

    /**
     * Applies a translation.
     *
     * @param x  the translation along the x-axis.
     * @param y  the translation along the y-axis.
     */
    public void translate(int x, int y) {
        Transform swtTransform = new Transform(this.gc.getDevice());
        this.gc.getTransform(swtTransform);
        swtTransform.translate(x, y);
        this.gc.setTransform(swtTransform);
        swtTransform.dispose();
    }

    /**
     * Applies a translation.
     *
     * @param tx  the translation along the x-axis.
     * @param ty  the translation along the y-axis.
     */
    public void translate(double tx, double ty) {
        translate((int) tx, (int) ty);
    }

    /**
     * Applies a rotation transform.
     *
     * @param theta  the angle of rotation.
     */
    public void rotate(double theta) {
        AffineTransform t = getTransform();
        t.rotate(theta);
        setTransform(t);
    }

    /**
     * Not implemented - see {@link Graphics2D#rotate(double, double, double)}.
     *
     * @see java.awt.Graphics2D#rotate(double, double, double)
     */
    public void rotate(double theta, double x, double y) {
        translate(x, y);
        rotate(theta);
        translate(-x, -y);
    }

    /**
     * Applies a scale transform.
     *
     * @param scaleX  the scale factor along the x-axis.
     * @param scaleY  the scale factor along the y-axis.
     */
    public void scale(double scaleX, double scaleY) {
        Transform swtTransform = new Transform(this.gc.getDevice());
        this.gc.getTransform(swtTransform);
        swtTransform.scale((float) scaleX, (float) scaleY);
        this.gc.setTransform(swtTransform);
        swtTransform.dispose();
    }

    /**
     * Applies a shear transform.
     *
     * @param shearX  the x-factor.
     * @param shearY  the y-factor.
     */
    public void shear(double shearX, double shearY) {
        transform(AffineTransform.getShearInstance(shearX, shearY));
    }

    /**
     * Draws the outline of the specified shape using the current stroke and
     * paint settings.
     *
     * @param shape  the shape (<code>null</code> not permitted).
     *
     * @see #getPaint()
     * @see #getStroke()
     * @see #fill(Shape)
     */
    public void draw(Shape shape) {
        Path path = toSwtPath(shape);
        this.gc.drawPath(path);
        path.dispose();
    }

    /**
     * Draws a line from (x1, y1) to (x2, y2) using the current stroke
     * and paint settings.
     *
     * @param x1  the x-coordinate for the starting point.
     * @param y1  the y-coordinate for the starting point.
     * @param x2  the x-coordinate for the ending point.
     * @param y2  the y-coordinate for the ending point.
     *
     * @see #draw(Shape)
     */
    public void drawLine(int x1, int y1, int x2, int y2) {
        this.gc.drawLine(x1, y1, x2, y2);
    }

    /**
     * Draws the outline of the polygon specified by the given points, using
     * the current paint and stroke settings.
     *
     * @param xPoints  the x-coordinates.
     * @param yPoints  the y-coordinates.
     * @param npoints  the number of points in the polygon.
     *
     * @see #draw(Shape)
     */
    public void drawPolygon(int [] xPoints, int [] yPoints, int npoints) {
        drawPolyline(xPoints, yPoints, npoints);
        if (npoints > 1) {
            this.gc.drawLine(xPoints[npoints - 1], yPoints[npoints - 1],
                    xPoints[0], yPoints[0]);
        }
    }

    /**
     * Draws a sequence of connected lines specified by the given points, using
     * the current paint and stroke settings.
     *
     * @param xPoints  the x-coordinates.
     * @param yPoints  the y-coordinates.
     * @param npoints  the number of points in the polygon.
     *
     * @see #draw(Shape)
     */
    public void drawPolyline(int [] xPoints, int [] yPoints, int npoints) {
        if (npoints > 1) {
            int x0 = xPoints[0];
            int y0 = yPoints[0];
            int x1 = 0, y1 = 0;
            for (int i = 1; i < npoints; i++) {
                x1 = xPoints[i];
                y1 = yPoints[i];
                this.gc.drawLine(x0, y0, x1, y1);
                x0 = x1;
                y0 = y1;
            }
        }
    }

    /**
     * Draws an oval that fits within the specified rectangular region.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the frame width.
     * @param height  the frame height.
     *
     * @see #fillOval(int, int, int, int)
     * @see #draw(Shape)
     */
    public void drawOval(int x, int y, int width, int height) {
        this.gc.drawOval(x, y, width - 1, height - 1);
    }

    /**
     * Draws an arc that is part of an ellipse that fits within the specified
     * framing rectangle.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the frame width.
     * @param height  the frame height.
     * @param arcStart  the arc starting point, in degrees.
     * @param arcAngle  the extent of the arc.
     *
     * @see #fillArc(int, int, int, int, int, int)
     */
    public void drawArc(int x, int y, int width, int height, int arcStart,
            int arcAngle) {
        this.gc.drawArc(x, y, width - 1, height - 1, arcStart, arcAngle);
    }

    /**
     * Draws a rectangle with rounded corners that fits within the specified
     * framing rectangle.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the frame width.
     * @param height  the frame height.
     * @param arcWidth  the width of the arc defining the roundedness of the
     *         rectangle's corners.
     * @param arcHeight the height of the arc defining the roundedness of the
     *         rectangle's corners.
     *
     * @see #fillRoundRect(int, int, int, int, int, int)
     */
    public void drawRoundRect(int x, int y, int width, int height,
            int arcWidth, int arcHeight) {
        this.gc.drawRoundRectangle(x, y, width - 1, height - 1, arcWidth,
                arcHeight);
    }

    /**
     * Fills the specified shape using the current paint.
     *
     * @param shape  the shape (<code>null</code> not permitted).
     *
     * @see #getPaint()
     * @see #draw(Shape)
     */
    public void fill(Shape shape) {
        Path path = toSwtPath(shape);
        // Note that for consistency with the AWT implementation, it is
        // necessary to switch temporarily the foreground and background
        // colours
        switchColors();
        this.gc.fillPath(path);
        switchColors();
        path.dispose();
    }

    /**
     * Fill a rectangle area on the swt graphic composite.
     * The <code>fillRectangle</code> method of the <code>GC</code>
     * class uses the background color so we must switch colors.
     * @see java.awt.Graphics#fillRect(int, int, int, int)
     */
    public void fillRect(int x, int y, int width, int height) {
        this.switchColors();
        this.gc.fillRectangle(x, y, width, height);
        this.switchColors();
    }

    /**
     * Fills the specified rectangle with the current background colour.
     *
     * @param x  the x-coordinate for the rectangle.
     * @param y  the y-coordinate for the rectangle.
     * @param width  the width.
     * @param height  the height.
     *
     * @see #fillRect(int, int, int, int)
     */
    public void clearRect(int x, int y, int width, int height) {
        Color bgcolor = getBackground();
        if (bgcolor == null) {
            return;  // we can't do anything
        }
        Paint saved = getPaint();
        setPaint(bgcolor);
        fillRect(x, y, width, height);
        setPaint(saved);
    }

    /**
     * Fills the specified polygon.
     *
     * @param xPoints  the x-coordinates.
     * @param yPoints  the y-coordinates.
     * @param npoints  the number of points.
     */
    public void fillPolygon(int[] xPoints, int[] yPoints, int npoints) {
        int[] pointArray = new int[npoints * 2];
        for (int i = 0; i < npoints; i++) {
            pointArray[2 * i] = xPoints[i];
            pointArray[2 * i + 1] = yPoints[i];
        }
        switchColors();
        this.gc.fillPolygon(pointArray);
        switchColors();
    }

    /**
     * Draws a rectangle with rounded corners that fits within the specified
     * framing rectangle.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the frame width.
     * @param height  the frame height.
     * @param arcWidth  the width of the arc defining the roundedness of the
     *         rectangle's corners.
     * @param arcHeight the height of the arc defining the roundedness of the
     *         rectangle's corners.
     *
     * @see #drawRoundRect(int, int, int, int, int, int)
     */
    public void fillRoundRect(int x, int y, int width, int height,
            int arcWidth, int arcHeight) {
        switchColors();
        this.gc.fillRoundRectangle(x, y, width - 1, height - 1, arcWidth,
                arcHeight);
        switchColors();
    }

    /**
     * Fills an oval that fits within the specified rectangular region.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the frame width.
     * @param height  the frame height.
     *
     * @see #drawOval(int, int, int, int)
     * @see #fill(Shape)
     */
    public void fillOval(int x, int y, int width, int height) {
        switchColors();
        this.gc.fillOval(x, y, width - 1, height - 1);
        switchColors();
    }

    /**
     * Fills an arc that is part of an ellipse that fits within the specified
     * framing rectangle.
     *
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the frame width.
     * @param height  the frame height.
     * @param arcStart  the arc starting point, in degrees.
     * @param arcAngle  the extent of the arc.
     *
     * @see #drawArc(int, int, int, int, int, int)
     */
    public void fillArc(int x, int y, int width, int height, int arcStart,
            int arcAngle) {
        switchColors();
        this.gc.fillArc(x, y, width - 1, height - 1, arcStart, arcAngle);
        switchColors();
    }

    /**
     * Returns the font in form of an awt font created
     * with the parameters of the font of the swt graphic
     * composite.
     * @see java.awt.Graphics#getFont()
     */
    public Font getFont() {
        // retrieve the swt font description in an os indept way
        FontData[] fontData = this.gc.getFont().getFontData();
        // create a new awt font with the appropiate data
        return SWTUtils.toAwtFont(this.gc.getDevice(), fontData[0], true);
    }

    /**
     * Set the font swt graphic composite from the specified
     * awt font. Be careful that the newly created swt font
     * must be disposed separately.
     * @see java.awt.Graphics#setFont(java.awt.Font)
     */
    public void setFont(Font font) {
        org.eclipse.swt.graphics.Font swtFont = getSwtFontFromPool(font);
        this.gc.setFont(swtFont);
    }

    /**
     * Returns the font metrics.
     *
     * @param font the font.
     *
     * @return The font metrics.
     */
    public FontMetrics getFontMetrics(Font font) {
        return SWTUtils.DUMMY_PANEL.getFontMetrics(font);
    }

    /**
     * Returns the font render context.
     *
     * @return The font render context.
     */
    public FontRenderContext getFontRenderContext() {
        FontRenderContext fontRenderContext = new FontRenderContext(
                new AffineTransform(), true, true);
        return fontRenderContext;
    }

    /**
     * Draws the specified glyph vector at the location <code>(x, y)</code>.
     * 
     * @param g  the glyph vector (<code>null</code> not permitted).
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     */
    public void drawGlyphVector(GlyphVector g, float x, float y) {
        fill(g.getOutline(x, y));
    }

    /**
     * Draws a string on the receiver. note that
     * to be consistent with the awt method,
     * the y has to be modified with the ascent of the font.
     *
     * @see java.awt.Graphics#drawString(java.lang.String, int, int)
     */
    public void drawString(String text, int x, int y) {
        drawString(text, (float) x, (float) y);
    }

    /**
     * Draws a string at the specified position.
     *
     * @param text  the string.
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     */
    public void drawString(String text, float x, float y) {
        if (text == null) {
            throw new NullPointerException("Null 'text' argument.");
        }
        float fm = this.gc.getFontMetrics().getAscent();
        this.gc.drawString(text, (int) x, (int) (y - fm), true);
    }

    /**
     * Draws a string at the specified position.
     *
     * @param iterator  the string.
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     */
    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
        // for now we simply want to extract the chars from the iterator
        // and call an unstyled text renderer
        StringBuffer sb = new StringBuffer();
        int numChars = iterator.getEndIndex() - iterator.getBeginIndex();
        char c = iterator.first();
        for (int i = 0; i < numChars; i++) {
            sb.append(c);
            c = iterator.next();
        }
        drawString(new String(sb),x,y);
    }

    /**
     * Draws a string at the specified position.
     *
     * @param iterator  the string.
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     */
    public void drawString(AttributedCharacterIterator iterator, float x,
            float y) {
        drawString(iterator, (int) x, (int) y);
    }

    /**
     * Returns <code>true</code> if the rectangle (in device space) intersects
     * with the shape (the interior, if <code>onStroke</code> is false, 
     * otherwise the stroked outline of the shape).
     * 
     * @param rect  a rectangle (in device space).
     * @param s the shape.
     * @param onStroke  test the stroked outline only?
     * 
     * @return A boolean. 
     */
    @Override
    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
        AffineTransform transform = getTransform();
        Shape ts;
        if (onStroke) {
            Stroke stroke = getStroke();
            ts = transform.createTransformedShape(stroke.createStrokedShape(s));
        } else {
            ts = transform.createTransformedShape(s);
        }
        if (!rect.getBounds2D().intersects(ts.getBounds2D())) {
            return false;
        }
        Area a1 = new Area(rect);
        Area a2 = new Area(ts);
        a1.intersect(a2);
        return !a1.isEmpty();
    }

    /**
     * Not implemented - see {@link Graphics#copyArea(int, int, int, int, int,
     * int)}.
     */
    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
        // TODO Auto-generated method stub
    }

    /**
     * Not implemented - see {@link Graphics2D#drawImage(Image,
     * AffineTransform, ImageObserver)}.
     *
     * @param image  the image.
     * @param xform  the transform.
     * @param obs  an image observer.
     *
     * @return A boolean.
     */
    public boolean drawImage(Image image, AffineTransform xform,
            ImageObserver obs) {
        // TODO Auto-generated method stub
        return false;
    }

    /**
     * Draws an image.
     *
     * @param image  the image.
     * @param op  the image operation.
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     */
    public void drawImage(BufferedImage image, BufferedImageOp op, int x,
            int y) {
        org.eclipse.swt.graphics.Image im = new org.eclipse.swt.graphics.Image(
                this.gc.getDevice(), SWTUtils.convertToSWT(image));
        this.gc.drawImage(im, x, y);
        im.dispose();
    }

    /**
     * Draws an SWT image with the top left corner of the image aligned to the
     * point (x, y).
     *
     * @param image  the image.
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     */
    public void drawImage(org.eclipse.swt.graphics.Image image, int x, int y) {
        this.gc.drawImage(image, x, y);
    }

    /**
     * Not implemented - see {@link Graphics2D#drawRenderedImage(RenderedImage,
     * AffineTransform)}.
     *
     * @param image  the image.
     * @param xform  the transform.
     */
    public void drawRenderedImage(RenderedImage image, AffineTransform xform) {
        // TODO Auto-generated method stub
    }

    /**
     * Not implemented - see {@link Graphics2D#drawRenderableImage(
     * RenderableImage, AffineTransform)}.
     *
     * @param image  the image.
     * @param xform  the transform.
     */
    public void drawRenderableImage(RenderableImage image,
            AffineTransform xform) {
        // TODO Auto-generated method stub

    }

    /**
     * Draws an image with the top left corner aligned to the point (x, y).
     *
     * @param image  the image.
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param observer  ignored here.
     *
     * @return <code>true</code> if the image has been drawn.
     */
    public boolean drawImage(Image image, int x, int y,
            ImageObserver observer) {
        ImageData data = SWTUtils.convertAWTImageToSWT(image);
        if (data == null) {
            return false;
        }
        org.eclipse.swt.graphics.Image im = new org.eclipse.swt.graphics.Image(
                this.gc.getDevice(), data);
        this.gc.drawImage(im, x, y);
        im.dispose();
        return true;
    }

    /**
     * Draws an image with the top left corner aligned to the point (x, y),
     * and scaled to the specified width and height.
     *
     * @param image  the image.
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the width for the rendered image.
     * @param height  the height for the rendered image.
     * @param observer  ignored here.
     *
     * @return <code>true</code> if the image has been drawn.
     */
    public boolean drawImage(Image image, int x, int y, int width, int height,
            ImageObserver observer) {
        ImageData data = SWTUtils.convertAWTImageToSWT(image);
        if (data == null) {
            return false;
        }
        org.eclipse.swt.graphics.Image im = new org.eclipse.swt.graphics.Image(
                this.gc.getDevice(), data);
        org.eclipse.swt.graphics.Rectangle bounds = im.getBounds();
        this.gc.drawImage(im, 0, 0, bounds.width, bounds.height, x, y, width,
                height);
        im.dispose();
        return true;
    }

    /**
     * Draws an image.
     *
     * @param image (<code>null</code> not permitted).
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param bgcolor  the background color.
     * @param observer  an image observer.
     *
     * @return A boolean.
     */
    public boolean drawImage(Image image, int x, int y, Color bgcolor,
            ImageObserver observer) {
        ParamChecks.nullNotPermitted(image, "image");
        int w = image.getWidth(null);
        int h = image.getHeight(null);
        if (w == -1 || h == -1) {
            return false;
        }
        Paint savedPaint = getPaint();
        fill(new Rectangle2D.Double(x, y, w, h));
        setPaint(savedPaint);
        return drawImage(image, x, y, observer);
    }

    /**
     * Draws an image.
     *
     * @param image  the image (<code>null</code> not permitted).
     * @param x  the x-coordinate.
     * @param y  the y-coordinate.
     * @param width  the width.
     * @param height  the height.
     * @param bgcolor  the background colour.
     * @param observer  an image observer.
     *
     * @return A boolean.
     */
    public boolean drawImage(Image image, int x, int y, int width, int height,
            Color bgcolor, ImageObserver observer) {
        ParamChecks.nullNotPermitted(image, "image");
        int w = image.getWidth(null);
        int h = image.getHeight(null);
        if (w == -1 || h == -1) {
            return false;
        }
        Paint savedPaint = getPaint();
        fill(new Rectangle2D.Double(x, y, w, h));
        setPaint(savedPaint);
        return drawImage(image, x, y, width, height, observer);
    }

    /**
     * Not implemented - see {@link Graphics#drawImage(Image, int, int, int,
     *     int, int, int, int, int, ImageObserver)}.
     *
     * @param image  the image.
     * @param dx1
     * @param dy1
     * @param dx2
     * @param dy2
     * @param sx1
     * @param sy1
     * @param sx2
     * @param sy2
     * @param observer
     */
    public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
            int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
        // TODO Auto-generated method stub
        return false;
    }

    /**
     * Not implemented - see {@link Graphics#drawImage(Image, int, int, int,
     *     int, int, int, int, int, Color, ImageObserver)}.
     *
     * @param image  the image.
     * @param dx1
     * @param dy1
     * @param dx2
     * @param dy2
     * @param sx1
     * @param sy1
     * @param sx2
     * @param sy2
     * @param bgcolor
     * @param observer
     */
    public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2,
            int sx1, int sy1, int sx2, int sy2, Color bgcolor,
            ImageObserver observer) {
        // TODO Auto-generated method stub
        return false;
    }

    /**
     * Releases resources held by this instance (but note that the caller
     * must dispose of the 'GC' passed to the constructor).
     *
     * @see java.awt.Graphics#dispose()
     */
    public void dispose() {
        // we dispose resources we own but user must dispose gc
        disposeResourcePool();
    }

    /**
     * Add given swt resource to the resource pool. All resources added
     * to the resource pool will be disposed when {@link #dispose()} is called.
     *
     * @param resource the resource to add to the pool.
     * @return the swt <code>Resource</code> just added.
     */
    private Resource addToResourcePool(Resource resource) {
        this.resourcePool.add(resource);
        return resource;
    }

    /**
     * Dispose the resource pool.
     */
    private void disposeResourcePool() {
        for (Iterator it = this.resourcePool.iterator(); it.hasNext();) {
            Resource resource = (Resource) it.next();
            resource.dispose();
        }
        this.fontsPool.clear();
        this.colorsPool.clear();
        this.transformsPool.clear();
        this.resourcePool.clear();
    }

    /**
     * Internal method to convert a AWT font object into
     * a SWT font resource. If a corresponding SWT font
     * instance is already in the pool, it will be used
     * instead of creating a new one. This is used in
     * {@link #setFont()} for instance.
     *
     * @param font The AWT font to convert.
     * @return The SWT font instance.
     */
    private org.eclipse.swt.graphics.Font getSwtFontFromPool(Font font) {
        org.eclipse.swt.graphics.Font swtFont = (org.eclipse.swt.graphics.Font)
        this.fontsPool.get(font);
        if (swtFont == null) {
            swtFont = new org.eclipse.swt.graphics.Font(this.gc.getDevice(),
                    SWTUtils.toSwtFontData(this.gc.getDevice(), font, true));
            addToResourcePool(swtFont);
            this.fontsPool.put(font, swtFont);
        }
        return swtFont;
    }

    /**
     * Internal method to convert a AWT color object into
     * a SWT color resource. If a corresponding SWT color
     * instance is already in the pool, it will be used
     * instead of creating a new one. This is used in
     * {@link #setColor()} for instance.
     *
     * @param awtColor The AWT color to convert.
     * @return A SWT color instance.
     */
    private org.eclipse.swt.graphics.Color getSwtColorFromPool(Color awtColor) {
        org.eclipse.swt.graphics.Color swtColor =
                (org.eclipse.swt.graphics.Color)
                // we can't use the following valueOf() method, because it
                // won't compile with JDK1.4
                // this.colorsPool.get(Integer.valueOf(awtColor.getRGB()));
                this.colorsPool.get(new Integer(awtColor.getRGB()));
        if (swtColor == null) {
            swtColor = SWTUtils.toSwtColor(this.gc.getDevice(), awtColor);
            addToResourcePool(swtColor);
            // see comment above
            //this.colorsPool.put(Integer.valueOf(awtColor.getRGB()), swtColor);
            this.colorsPool.put(new Integer(awtColor.getRGB()), swtColor);
        }
        return swtColor;
    }

    /**
     * Internal method to convert a AWT transform object into
     * a SWT transform resource. If a corresponding SWT transform
     * instance is already in the pool, it will be used
     * instead of creating a new one. This is used in
     * {@link #setTransform()} for instance.
     *
     * @param awtTransform The AWT transform to convert.
     * @return A SWT transform instance.
     */
    private Transform getSwtTransformFromPool(AffineTransform awtTransform) {
        Transform t = (Transform) this.transformsPool.get(awtTransform);
        if (t == null) {
            t = new Transform(this.gc.getDevice());
            double[] matrix = new double[6];
            awtTransform.getMatrix(matrix);
            t.setElements((float) matrix[0], (float) matrix[1],
                    (float) matrix[2], (float) matrix[3],
                    (float) matrix[4], (float) matrix[5]);
            addToResourcePool(t);
            this.transformsPool.put(awtTransform, t);
        }
        return t;
    }

    /**
     * Perform a switch between foreground and background
     * color of gc. This is needed for consistency with
     * the awt behaviour, and is required notably for the
     * filling methods.
     */
    private void switchColors() {
        org.eclipse.swt.graphics.Color bg = this.gc.getBackground();
        org.eclipse.swt.graphics.Color fg = this.gc.getForeground();
        this.gc.setBackground(fg);
        this.gc.setForeground(bg);
    }

    /**
     * Converts an AWT <code>Shape</code> into a SWT <code>Path</code>.
     *
     * @param shape  the shape (<code>null</code> not permitted).
     *
     * @return The path.
     */
    private Path toSwtPath(Shape shape) {
        int type;
        float[] coords = new float[6];
        Path path = new Path(this.gc.getDevice());
        PathIterator pit = shape.getPathIterator(null);
        while (!pit.isDone()) {
            type = pit.currentSegment(coords);
            switch (type) {
                case (PathIterator.SEG_MOVETO):
                    path.moveTo(coords[0], coords[1]);
                    break;
                case (PathIterator.SEG_LINETO):
                    path.lineTo(coords[0], coords[1]);
                    break;
                case (PathIterator.SEG_QUADTO):
                    path.quadTo(coords[0], coords[1], coords[2], coords[3]);
                    break;
                case (PathIterator.SEG_CUBICTO):
                    path.cubicTo(coords[0], coords[1], coords[2],
                            coords[3], coords[4], coords[5]);
                    break;
                case (PathIterator.SEG_CLOSE):
                    path.close();
                    break;
                default:
                    break;
            }
            pit.next();
        }
        return path;
    }

    /**
     * Converts an SWT transform into the equivalent AWT transform.
     *
     * @param swtTransform  the SWT transform.
     *
     * @return The AWT transform.
     */
    private AffineTransform toAwtTransform(Transform swtTransform) {
        float[] elements = new float[6];
        swtTransform.getElements(elements);
        AffineTransform awtTransform = new AffineTransform(elements);
        return awtTransform;
    }

    /**
     * Returns the AWT line cap corresponding to the specified SWT line cap.
     *
     * @param swtLineCap  the SWT line cap.
     *
     * @return The AWT line cap.
     */
    private int toAwtLineCap(int swtLineCap) {
        if (swtLineCap == SWT.CAP_FLAT) {
            return BasicStroke.CAP_BUTT;
        }
        else if (swtLineCap == SWT.CAP_ROUND) {
            return BasicStroke.CAP_ROUND;
        }
        else if (swtLineCap == SWT.CAP_SQUARE) {
            return BasicStroke.CAP_SQUARE;
        }
        else {
            throw new IllegalArgumentException("SWT LineCap " + swtLineCap
                + " not recognised");
        }
    }

    /**
     * Returns the AWT line join corresponding to the specified SWT line join.
     *
     * @param swtLineJoin  the SWT line join.
     *
     * @return The AWT line join.
     */
    private int toAwtLineJoin(int swtLineJoin) {
        if (swtLineJoin == SWT.JOIN_BEVEL) {
            return BasicStroke.JOIN_BEVEL;
        }
        else if (swtLineJoin == SWT.JOIN_MITER) {
            return BasicStroke.JOIN_MITER;
        }
        else if (swtLineJoin == SWT.JOIN_ROUND) {
            return BasicStroke.JOIN_ROUND;
        }
        else {
            throw new IllegalArgumentException("SWT LineJoin " + swtLineJoin
                + " not recognised");
        }
    }

    /**
     * Returns the SWT line cap corresponding to the specified AWT line cap.
     *
     * @param awtLineCap  the AWT line cap.
     *
     * @return The SWT line cap.
     */
    private int toSwtLineCap(int awtLineCap) {
        if (awtLineCap == BasicStroke.CAP_BUTT) {
            return SWT.CAP_FLAT;
        }
        else if (awtLineCap == BasicStroke.CAP_ROUND) {
            return SWT.CAP_ROUND;
        }
        else if (awtLineCap == BasicStroke.CAP_SQUARE) {
            return SWT.CAP_SQUARE;
        }
        else {
            throw new IllegalArgumentException("AWT LineCap " + awtLineCap
                + " not recognised");
        }
    }

    /**
     * Returns the SWT line join corresponding to the specified AWT line join.
     *
     * @param awtLineJoin  the AWT line join.
     *
     * @return The SWT line join.
     */
    private int toSwtLineJoin(int awtLineJoin) {
        if (awtLineJoin == BasicStroke.JOIN_BEVEL) {
            return SWT.JOIN_BEVEL;
        }
        else if (awtLineJoin == BasicStroke.JOIN_MITER) {
            return SWT.JOIN_MITER;
        }
        else if (awtLineJoin == BasicStroke.JOIN_ROUND) {
            return SWT.JOIN_ROUND;
        }
        else {
            throw new IllegalArgumentException("AWT LineJoin " + awtLineJoin
                + " not recognised");
        }
    }
}
