PaintExample.java Source code

Java tutorial

Introduction

Here is the source code for PaintExample.java

Source

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.accessibility.*;

import java.io.*;
import java.text.*;
import java.util.*;

public class PaintExample {
    private Composite mainComposite;
    private Canvas activeForegroundColorCanvas;
    private Canvas activeBackgroundColorCanvas;
    private Color paintColorBlack, paintColorWhite; // alias for paintColors[0] and [1]
    private Color[] paintColors;
    private Font paintDefaultFont; // do not free
    private static final int numPaletteRows = 3;
    private static final int numPaletteCols = 50;
    private ToolSettings toolSettings; // current active settings
    private PaintSurface paintSurface; // paint surface for drawing

    static final int Pencil_tool = 0;
    static final int Airbrush_tool = 1;
    static final int Line_tool = 2;
    static final int PolyLine_tool = 3;
    static final int Rectangle_tool = 4;
    static final int RoundedRectangle_tool = 5;
    static final int Ellipse_tool = 6;
    static final int Text_tool = 7;
    static final int None_fill = 8;
    static final int Outline_fill = 9;
    static final int Solid_fill = 10;
    static final int Solid_linestyle = 11;
    static final int Dash_linestyle = 12;
    static final int Dot_linestyle = 13;
    static final int DashDot_linestyle = 14;
    static final int Font_options = 15;

    static final int Default_tool = Pencil_tool;
    static final int Default_fill = None_fill;
    static final int Default_linestyle = Solid_linestyle;

    public static final Tool[] tools = { new Tool(Pencil_tool, "Pencil", "tool", SWT.RADIO),
            new Tool(Airbrush_tool, "Airbrush", "tool", SWT.RADIO), new Tool(Line_tool, "Line", "tool", SWT.RADIO),
            new Tool(PolyLine_tool, "PolyLine", "tool", SWT.RADIO),
            new Tool(Rectangle_tool, "Rectangle", "tool", SWT.RADIO),
            new Tool(RoundedRectangle_tool, "RoundedRectangle", "tool", SWT.RADIO),
            new Tool(Ellipse_tool, "Ellipse", "tool", SWT.RADIO), new Tool(Text_tool, "Text", "tool", SWT.RADIO),
            new Tool(None_fill, "None", "fill", SWT.RADIO, new Integer(ToolSettings.ftNone)),
            new Tool(Outline_fill, "Outline", "fill", SWT.RADIO, new Integer(ToolSettings.ftOutline)),
            new Tool(Solid_fill, "Solid", "fill", SWT.RADIO, new Integer(ToolSettings.ftSolid)),
            new Tool(Solid_linestyle, "Solid", "linestyle", SWT.RADIO, new Integer(SWT.LINE_SOLID)),
            new Tool(Dash_linestyle, "Dash", "linestyle", SWT.RADIO, new Integer(SWT.LINE_DASH)),
            new Tool(Dot_linestyle, "Dot", "linestyle", SWT.RADIO, new Integer(SWT.LINE_DOT)),
            new Tool(DashDot_linestyle, "DashDot", "linestyle", SWT.RADIO, new Integer(SWT.LINE_DASHDOT)),
            new Tool(Font_options, "Font", "options", SWT.PUSH) };

    /**
     * Creates an instance of a PaintExample embedded inside
     * the supplied parent Composite.
     * 
     * @param parent the container of the example
     */
    public PaintExample(Composite parent) {
        mainComposite = parent;
        initResources();
        initActions();
        init();
    }

    /**
     * Invokes as a standalone program.
     */
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText(getResourceString("window.title"));
        shell.setLayout(new GridLayout());
        PaintExample instance = new PaintExample(shell);
        instance.createToolBar(shell);
        Composite composite = new Composite(shell, SWT.NONE);
        composite.setLayout(new FillLayout());
        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        instance.createGUI(composite);
        instance.setDefaults();
        setShellSize(display, shell);
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        instance.dispose();
    }

    /**
     * Creates the toolbar.
     * Note: Only called by standalone.
     */
    private void createToolBar(Composite parent) {
        ToolBar toolbar = new ToolBar(parent, SWT.NONE);
        String group = null;
        for (int i = 0; i < tools.length; i++) {
            Tool tool = tools[i];
            if (group != null && !tool.group.equals(group)) {
                new ToolItem(toolbar, SWT.SEPARATOR);
            }
            group = tool.group;
            ToolItem item = addToolItem(toolbar, tool);
            if (i == Default_tool || i == Default_fill || i == Default_linestyle)
                item.setSelection(true);
        }
    }

    /**
     * Adds a tool item to the toolbar.
     * Note: Only called by standalone.
     */
    private ToolItem addToolItem(final ToolBar toolbar, final Tool tool) {
        final String id = tool.group + '.' + tool.name;
        ToolItem item = new ToolItem(toolbar, tool.type);
        item.setText(getResourceString(id + ".label"));
        item.setToolTipText(getResourceString(id + ".tooltip"));
        item.setImage(tool.image);
        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                tool.action.run();
            }
        });
        final int childID = toolbar.indexOf(item);
        toolbar.getAccessible().addAccessibleListener(new AccessibleAdapter() {
            public void getName(org.eclipse.swt.accessibility.AccessibleEvent e) {
                if (e.childID == childID) {
                    e.result = getResourceString(id + ".description");
                }
            }
        });
        return item;
    }

    /**
     * Sets the default tool item states.
     */
    public void setDefaults() {
        setPaintTool(Default_tool);
        setFillType(Default_fill);
        setLineStyle(Default_linestyle);
        setForegroundColor(paintColorBlack);
        setBackgroundColor(paintColorWhite);
    }

    /**
     * Creates the GUI.
     */
    public void createGUI(Composite parent) {
        GridLayout gridLayout;
        GridData gridData;

        /*** Create principal GUI layout elements ***/
        Composite displayArea = new Composite(parent, SWT.NONE);
        gridLayout = new GridLayout();
        gridLayout.numColumns = 1;
        displayArea.setLayout(gridLayout);

        // Creating these elements here avoids the need to instantiate the GUI elements
        // in strict layout order.  The natural layout ordering is an artifact of using
        // SWT layouts, but unfortunately it is not the same order as that required to
        // instantiate all of the non-GUI application elements to satisfy referential
        // dependencies.  It is possible to reorder the initialization to some extent, but
        // this can be very tedious.

        // paint canvas
        final Canvas paintCanvas = new Canvas(displayArea,
                SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
        paintCanvas.setLayoutData(gridData);
        paintCanvas.setBackground(paintColorWhite);

        // color selector frame
        final Composite colorFrame = new Composite(displayArea, SWT.NONE);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL);
        colorFrame.setLayoutData(gridData);

        // tool settings frame
        final Composite toolSettingsFrame = new Composite(displayArea, SWT.NONE);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL);
        toolSettingsFrame.setLayoutData(gridData);

        // status text
        final Text statusText = new Text(displayArea, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY);
        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL);
        statusText.setLayoutData(gridData);

        /*** Create the remaining application elements inside the principal GUI layout elements ***/
        // paintSurface
        paintSurface = new PaintSurface(paintCanvas, statusText, paintColorWhite);

        // finish initializing the tool data
        tools[Pencil_tool].data = new PencilTool(toolSettings, paintSurface);
        tools[Airbrush_tool].data = new AirbrushTool(toolSettings, paintSurface);
        tools[Line_tool].data = new LineTool(toolSettings, paintSurface);
        tools[PolyLine_tool].data = new PolyLineTool(toolSettings, paintSurface);
        tools[Rectangle_tool].data = new RectangleTool(toolSettings, paintSurface);
        tools[RoundedRectangle_tool].data = new RoundedRectangleTool(toolSettings, paintSurface);
        tools[Ellipse_tool].data = new EllipseTool(toolSettings, paintSurface);
        tools[Text_tool].data = new TextTool(toolSettings, paintSurface);

        // colorFrame      
        gridLayout = new GridLayout();
        gridLayout.numColumns = 3;
        gridLayout.marginHeight = 0;
        gridLayout.marginWidth = 0;
        colorFrame.setLayout(gridLayout);

        // activeForegroundColorCanvas, activeBackgroundColorCanvas
        activeForegroundColorCanvas = new Canvas(colorFrame, SWT.BORDER);
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
        gridData.heightHint = 24;
        gridData.widthHint = 24;
        activeForegroundColorCanvas.setLayoutData(gridData);

        activeBackgroundColorCanvas = new Canvas(colorFrame, SWT.BORDER);
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
        gridData.heightHint = 24;
        gridData.widthHint = 24;
        activeBackgroundColorCanvas.setLayoutData(gridData);

        // paletteCanvas
        final Canvas paletteCanvas = new Canvas(colorFrame, SWT.BORDER | SWT.NO_BACKGROUND);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.heightHint = 24;
        paletteCanvas.setLayoutData(gridData);
        paletteCanvas.addListener(SWT.MouseDown, new Listener() {
            public void handleEvent(Event e) {
                Rectangle bounds = paletteCanvas.getClientArea();
                Color color = getColorAt(bounds, e.x, e.y);

                if (e.button == 1)
                    setForegroundColor(color);
                else
                    setBackgroundColor(color);
            }

            private Color getColorAt(Rectangle bounds, int x, int y) {
                if (bounds.height <= 1 && bounds.width <= 1)
                    return paintColorWhite;
                final int row = (y - bounds.y) * numPaletteRows / bounds.height;
                final int col = (x - bounds.x) * numPaletteCols / bounds.width;
                return paintColors[Math.min(Math.max(row * numPaletteCols + col, 0), paintColors.length - 1)];
            }
        });
        Listener refreshListener = new Listener() {
            public void handleEvent(Event e) {
                if (e.gc == null)
                    return;
                Rectangle bounds = paletteCanvas.getClientArea();
                for (int row = 0; row < numPaletteRows; ++row) {
                    for (int col = 0; col < numPaletteCols; ++col) {
                        final int x = bounds.width * col / numPaletteCols;
                        final int y = bounds.height * row / numPaletteRows;
                        final int width = Math.max(bounds.width * (col + 1) / numPaletteCols - x, 1);
                        final int height = Math.max(bounds.height * (row + 1) / numPaletteRows - y, 1);
                        e.gc.setBackground(paintColors[row * numPaletteCols + col]);
                        e.gc.fillRectangle(bounds.x + x, bounds.y + y, width, height);
                    }
                }
            }
        };
        paletteCanvas.addListener(SWT.Resize, refreshListener);
        paletteCanvas.addListener(SWT.Paint, refreshListener);
        //paletteCanvas.redraw();

        // toolSettingsFrame
        gridLayout = new GridLayout();
        gridLayout.numColumns = 4;
        gridLayout.marginHeight = 0;
        gridLayout.marginWidth = 0;
        toolSettingsFrame.setLayout(gridLayout);

        Label label = new Label(toolSettingsFrame, SWT.NONE);
        label.setText(getResourceString("settings.AirbrushRadius.text"));

        final Scale airbrushRadiusScale = new Scale(toolSettingsFrame, SWT.HORIZONTAL);
        airbrushRadiusScale.setMinimum(5);
        airbrushRadiusScale.setMaximum(50);
        airbrushRadiusScale.setSelection(toolSettings.airbrushRadius);
        airbrushRadiusScale.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
        airbrushRadiusScale.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                toolSettings.airbrushRadius = airbrushRadiusScale.getSelection();
                updateToolSettings();
            }
        });

        label = new Label(toolSettingsFrame, SWT.NONE);
        label.setText(getResourceString("settings.AirbrushIntensity.text"));

        final Scale airbrushIntensityScale = new Scale(toolSettingsFrame, SWT.HORIZONTAL);
        airbrushIntensityScale.setMinimum(1);
        airbrushIntensityScale.setMaximum(100);
        airbrushIntensityScale.setSelection(toolSettings.airbrushIntensity);
        airbrushIntensityScale.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));
        airbrushIntensityScale.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                toolSettings.airbrushIntensity = airbrushIntensityScale.getSelection();
                updateToolSettings();
            }
        });
    }

    /**
     * Disposes of all resources associated with a particular
     * instance of the PaintExample.
     */
    public void dispose() {
        if (paintSurface != null)
            paintSurface.dispose();
        if (paintColors != null) {
            for (int i = 0; i < paintColors.length; ++i) {
                final Color color = paintColors[i];
                if (color != null)
                    color.dispose();
            }
        }
        paintDefaultFont = null;
        paintColors = null;
        paintSurface = null;
        freeResources();
    }

    /**
     * Frees the resource bundle resources.
     */
    public void freeResources() {
        for (int i = 0; i < tools.length; ++i) {
            Tool tool = tools[i];
            final Image image = tool.image;
            if (image != null)
                image.dispose();
            tool.image = null;
        }
    }

    /**
     * Returns the Display.
     * 
     * @return the display we're using
     */
    public Display getDisplay() {
        return mainComposite.getDisplay();
    }

    /**
     * Gets a string from the resource bundle.
     * We don't want to crash because of a missing String.
     * Returns the key if not found.
     */
    public static String getResourceString(String key) {
        return key;
    }

    /**
     * Gets a string from the resource bundle and binds it
     * with the given arguments. If the key is not found,
     * return the key.
     */
    public static String getResourceString(String key, Object[] args) {
        try {
            return MessageFormat.format(getResourceString(key), args);
        } catch (MissingResourceException e) {
            return key;
        } catch (NullPointerException e) {
            return "!" + key + "!";
        }
    }

    /**
     * Initialize colors, fonts, and tool settings.
     */
    private void init() {
        Display display = mainComposite.getDisplay();

        paintColorWhite = new Color(display, 255, 255, 255);
        paintColorBlack = new Color(display, 0, 0, 0);

        paintDefaultFont = display.getSystemFont();

        paintColors = new Color[numPaletteCols * numPaletteRows];
        paintColors[0] = paintColorBlack;
        paintColors[1] = paintColorWhite;
        for (int i = 2; i < paintColors.length; i++) {
            paintColors[i] = new Color(display, ((i * 7) % 255), ((i * 23) % 255), ((i * 51) % 255));
        }

        toolSettings = new ToolSettings();
        toolSettings.commonForegroundColor = paintColorBlack;
        toolSettings.commonBackgroundColor = paintColorWhite;
        toolSettings.commonFont = paintDefaultFont;
    }

    /**
     * Sets the action field of the tools
     */
    private void initActions() {
        for (int i = 0; i < tools.length; ++i) {
            final Tool tool = tools[i];
            String group = tool.group;
            if (group.equals("tool")) {
                tool.action = new Runnable() {
                    public void run() {
                        setPaintTool(tool.id);
                    }
                };
            } else if (group.equals("fill")) {
                tool.action = new Runnable() {
                    public void run() {
                        setFillType(tool.id);
                    }
                };
            } else if (group.equals("linestyle")) {
                tool.action = new Runnable() {
                    public void run() {
                        setLineStyle(tool.id);
                    }
                };
            } else if (group.equals("options")) {
                tool.action = new Runnable() {
                    public void run() {
                        FontDialog fontDialog = new FontDialog(paintSurface.getShell(), SWT.PRIMARY_MODAL);
                        FontData[] fontDatum = toolSettings.commonFont.getFontData();
                        if (fontDatum != null && fontDatum.length > 0) {
                            fontDialog.setFontList(fontDatum);
                        }
                        fontDialog.setText(getResourceString("options.Font.dialog.title"));

                        paintSurface.hideRubberband();
                        FontData fontData = fontDialog.open();
                        paintSurface.showRubberband();
                        if (fontData != null) {
                            try {
                                Font font = new Font(mainComposite.getDisplay(), fontData);
                                toolSettings.commonFont = font;
                                updateToolSettings();
                            } catch (SWTException ex) {
                            }
                        }
                    }
                };
            }
        }
    }

    /**
     * Loads the image resources.
     */
    public void initResources() {
        final Class clazz = PaintExample.class;
        try {
            for (int i = 0; i < tools.length; ++i) {
                Tool tool = tools[i];
                String id = tool.group + '.' + tool.name;
                InputStream sourceStream = clazz.getResourceAsStream(getResourceString(id + ".image"));
                ImageData source = new ImageData(sourceStream);
                ImageData mask = source.getTransparencyMask();
                tool.image = new Image(null, source, mask);
                try {
                    sourceStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return;
        } catch (Throwable t) {
        }

        String error = "Unable to load resources";
        freeResources();
        throw new RuntimeException(error);
    }

    /**
     * Grabs input focus.
     */
    public void setFocus() {
        mainComposite.setFocus();
    }

    /**
     * Sets the tool foreground color.
     * 
     * @param color the new color to use
     */
    public void setForegroundColor(Color color) {
        if (activeForegroundColorCanvas != null)
            activeForegroundColorCanvas.setBackground(color);
        toolSettings.commonForegroundColor = color;
        updateToolSettings();
    }

    /**
     * Set the tool background color.
     * 
     * @param color the new color to use
     */
    public void setBackgroundColor(Color color) {
        if (activeBackgroundColorCanvas != null)
            activeBackgroundColorCanvas.setBackground(color);
        toolSettings.commonBackgroundColor = color;
        updateToolSettings();
    }

    /**
     * Selects a tool given its ID.
     */
    public void setPaintTool(int id) {
        PaintTool paintTool = (PaintTool) tools[id].data;
        paintSurface.setPaintSession(paintTool);
        updateToolSettings();
    }

    /**
     * Selects a filltype given its ID.
     */
    public void setFillType(int id) {
        Integer fillType = (Integer) tools[id].data;
        toolSettings.commonFillType = fillType.intValue();
        updateToolSettings();
    }

    /**
     * Selects line type given its ID.
     */
    public void setLineStyle(int id) {
        Integer lineType = (Integer) tools[id].data;
        toolSettings.commonLineStyle = lineType.intValue();
        updateToolSettings();
    }

    /**
     * Sets the size of the shell to it's "packed" size,
     * unless that makes it bigger than the display,
     * in which case set it to 9/10 of display size.
     */
    private static void setShellSize(Display display, Shell shell) {
        Rectangle bounds = display.getBounds();
        Point size = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        if (size.x > bounds.width)
            size.x = bounds.width * 9 / 10;
        if (size.y > bounds.height)
            size.y = bounds.height * 9 / 10;
        shell.setSize(size);
    }

    /**
     * Notifies the tool that its settings have changed.
     */
    private void updateToolSettings() {
        final PaintTool activePaintTool = paintSurface.getPaintTool();
        if (activePaintTool == null)
            return;

        activePaintTool.endSession();
        activePaintTool.set(toolSettings);
        activePaintTool.beginSession();
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * Tool Settings objects group tool-related configuration information.
 */
class ToolSettings {
    public static final int ftNone = 0, ftOutline = 1, ftSolid = 2;

    /**
     * commonForegroundColor: current tool foreground colour
     */
    public Color commonForegroundColor;

    /**
     * commonBackgroundColor: current tool background colour
     */
    public Color commonBackgroundColor;

    /**
     * commonFont: current font
     */
    public Font commonFont;

    /**
     * commonFillType: current fill type
     * <p>One of ftNone, ftOutline, ftSolid.</p>
     */
    public int commonFillType = ftNone;

    /**
     * commonLineStyle: current line type
     */
    public int commonLineStyle = SWT.LINE_SOLID;

    /**
     * airbrushRadius: coverage radius in pixels
     */
    public int airbrushRadius = 10;

    /**
     * airbrushIntensity: average surface area coverage in region defined by radius per "jot"
     */
    public int airbrushIntensity = 30;

    /**
     * roundedRectangleCornerDiameter: the diameter of curvature of corners in a rounded rectangle
     */
    public int roundedRectangleCornerDiameter = 16;
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
class Tool {
    public int id;
    public String name;
    public String group;
    public int type;
    public Runnable action;
    public Image image = null;
    public Object data;

    public Tool(int id, String name, String group, int type) {
        super();
        this.id = id;
        this.name = name;
        this.group = group;
        this.type = type;
    }

    public Tool(int id, String name, String group, int type, Object data) {
        this(id, name, group, type);
        this.data = data;
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * A text drawing tool.
 */
class TextTool extends BasicPaintSession implements PaintTool {
    private ToolSettings settings;
    private String drawText = PaintExample.getResourceString("tool.Text.settings.defaulttext");

    /**
     * Constructs a PaintTool.
     * 
     * @param toolSettings the new tool settings
     * @param paintSurface the PaintSurface we will render on.
     */
    public TextTool(ToolSettings toolSettings, PaintSurface paintSurface) {
        super(paintSurface);
        set(toolSettings);
    }

    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings) {
        settings = toolSettings;
    }

    /**
     * Returns name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName() {
        return PaintExample.getResourceString("tool.Text.label");
    }

    /**
     * Activates the tool.
     */
    public void beginSession() {
        getPaintSurface().setStatusMessage(PaintExample.getResourceString("session.Text.message"));
    }

    /**
     * Deactivates the tool.
      */
    public void endSession() {
        getPaintSurface().clearRubberbandSelection();
    }

    /**
     * Aborts the current operation.
     */
    public void resetSession() {
        getPaintSurface().clearRubberbandSelection();
    }

    /**
     * Handles a mouseDown event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseDown(MouseEvent event) {
        if (event.button == 1) {
            // draw with left mouse button
            getPaintSurface().commitRubberbandSelection();
        } else {
            // set text with right mouse button
            getPaintSurface().clearRubberbandSelection();
            Shell shell = getPaintSurface().getShell();
            final Shell dialog = new Shell(shell, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
            dialog.setText(PaintExample.getResourceString("tool.Text.dialog.title"));
            dialog.setLayout(new GridLayout());
            Label label = new Label(dialog, SWT.NONE);
            label.setText(PaintExample.getResourceString("tool.Text.dialog.message"));
            label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
            final Text field = new Text(dialog, SWT.SINGLE | SWT.BORDER);
            field.setText(drawText);
            field.selectAll();
            field.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            Composite buttons = new Composite(dialog, SWT.NONE);
            GridLayout layout = new GridLayout(2, true);
            layout.marginWidth = 0;
            buttons.setLayout(layout);
            buttons.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
            Button ok = new Button(buttons, SWT.PUSH);
            ok.setText(PaintExample.getResourceString("OK"));
            ok.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
            ok.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent e) {
                    drawText = field.getText();
                    dialog.dispose();
                }
            });
            Button cancel = new Button(buttons, SWT.PUSH);
            cancel.setText(PaintExample.getResourceString("Cancel"));
            cancel.addSelectionListener(new SelectionAdapter() {
                public void widgetSelected(SelectionEvent e) {
                    dialog.dispose();
                }
            });
            dialog.setDefaultButton(ok);
            dialog.pack();
            dialog.open();
            Display display = dialog.getDisplay();
            while (!shell.isDisposed() && !dialog.isDisposed()) {
                if (!display.readAndDispatch())
                    display.sleep();
            }
        }
    }

    /**
     * Handles a mouseDoubleClick event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseDoubleClick(MouseEvent event) {
    }

    /**
     * Handles a mouseUp event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseUp(MouseEvent event) {
    }

    /**
     * Handles a mouseMove event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseMove(MouseEvent event) {
        final PaintSurface ps = getPaintSurface();
        ps.setStatusCoord(ps.getCurrentPosition());
        ps.clearRubberbandSelection();
        ps.addRubberbandSelection(
                new TextFigure(settings.commonForegroundColor, settings.commonFont, drawText, event.x, event.y));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * 2D Rectangle object
 */
class TextFigure extends Figure {
    private Color color;
    private Font font;
    private String text;
    private int x, y;

    /**
     * Constructs a TextFigure
     * 
     * @param color the color for this object
     * @param font  the font for this object
     * @param text  the text to draw, tab and new-line expansion is performed
     * @param x     the virtual X coordinate of the top-left corner of the text bounding box
     * @param y     the virtual Y coordinate of the top-left corner of the text bounding box
     */
    public TextFigure(Color color, Font font, String text, int x, int y) {
        this.color = color;
        this.font = font;
        this.text = text;
        this.x = x;
        this.y = y;
    }

    public void draw(FigureDrawContext fdc) {
        Point p = fdc.toClientPoint(x, y);
        fdc.gc.setFont(font);
        fdc.gc.setForeground(color);
        fdc.gc.drawText(text, p.x, p.y, true);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        Font oldFont = fdc.gc.getFont();
        fdc.gc.setFont(font);
        Point textExtent = fdc.gc.textExtent(text);
        fdc.gc.setFont(oldFont);
        region.add(fdc.toClientRectangle(x, y, x + textExtent.x, y + textExtent.y));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * 2D SolidRectangle object
 */
class SolidRoundedRectangleFigure extends Figure {
    private Color color;
    private int x1, y1, x2, y2, diameter;

    /**
     * Constructs a SolidRectangle
     * These objects are defined by any two diametrically opposing corners.
     * 
     * @param color the color for this object
     * @param x1 the virtual X coordinate of the first corner
     * @param y1 the virtual Y coordinate of the first corner
     * @param x2 the virtual X coordinate of the second corner
     * @param y2 the virtual Y coordinate of the second corner
     * @param diameter the diameter of curvature of all four corners
     */
    public SolidRoundedRectangleFigure(Color color, int x1, int y1, int x2, int y2, int diameter) {
        this.color = color;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        this.diameter = diameter;
    }

    public void draw(FigureDrawContext fdc) {
        Rectangle r = fdc.toClientRectangle(x1, y1, x2, y2);
        fdc.gc.setBackground(color);
        fdc.gc.fillRoundRectangle(r.x, r.y, r.width, r.height, diameter, diameter);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        region.add(fdc.toClientRectangle(x1, y1, x2, y2));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * 2D SolidRectangle object
 */
class SolidRectangleFigure extends Figure {
    private Color color;
    private int x1, y1, x2, y2;

    /**
     * Constructs a SolidRectangle
     * These objects are defined by any two diametrically opposing corners.
     * 
     * @param color the color for this object
     * @param x1 the virtual X coordinate of the first corner
     * @param y1 the virtual Y coordinate of the first corner
     * @param x2 the virtual X coordinate of the second corner
     * @param y2 the virtual Y coordinate of the second corner
     */
    public SolidRectangleFigure(Color color, int x1, int y1, int x2, int y2) {
        this.color = color;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    public void draw(FigureDrawContext fdc) {
        Rectangle r = fdc.toClientRectangle(x1, y1, x2, y2);
        fdc.gc.setBackground(color);
        fdc.gc.fillRectangle(r.x, r.y, r.width, r.height);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        region.add(fdc.toClientRectangle(x1, y1, x2, y2));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * 2D Line object
 */
class SolidPolygonFigure extends Figure {
    private Color color;
    private int[] points;

    /**
     * Constructs a SolidPolygon
     * These objects are defined by a sequence of vertices.
     * 
     * @param color the color for this object
     * @param vertices the array of vertices making up the polygon
     * @param numPoint the number of valid points in the array (n >= 3)
     */
    public SolidPolygonFigure(Color color, Point[] vertices, int numPoints) {
        this.color = color;
        this.points = new int[numPoints * 2];
        for (int i = 0; i < numPoints; ++i) {
            points[i * 2] = vertices[i].x;
            points[i * 2 + 1] = vertices[i].y;
        }
    }

    public void draw(FigureDrawContext fdc) {
        int[] drawPoints = new int[points.length];
        for (int i = 0; i < points.length; i += 2) {
            drawPoints[i] = points[i] * fdc.xScale - fdc.xOffset;
            drawPoints[i + 1] = points[i + 1] * fdc.yScale - fdc.yOffset;
        }
        fdc.gc.setBackground(color);
        fdc.gc.fillPolygon(drawPoints);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        int xmin = Integer.MAX_VALUE, ymin = Integer.MAX_VALUE;
        int xmax = Integer.MIN_VALUE, ymax = Integer.MIN_VALUE;

        for (int i = 0; i < points.length; i += 2) {
            if (points[i] < xmin)
                xmin = points[i];
            if (points[i] > xmax)
                xmax = points[i];
            if (points[i + 1] < ymin)
                ymin = points[i + 1];
            if (points[i + 1] > ymax)
                ymax = points[i + 1];
        }
        region.add(fdc.toClientRectangle(xmin, ymin, xmax, ymax));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * 2D Solid Ellipse object
 */
class SolidEllipseFigure extends Figure {
    private Color color;
    private int x1, y1, x2, y2;

    /**
     * Constructs a SolidEllipse
     * These objects are defined by any two diametrically opposing corners of a box
     * bounding the ellipse.
     * 
     * @param color the color for this object
     * @param x1 the virtual X coordinate of the first corner
     * @param y1 the virtual Y coordinate of the first corner
     * @param x2 the virtual X coordinate of the second corner
     * @param y2 the virtual Y coordinate of the second corner
     */
    public SolidEllipseFigure(Color color, int x1, int y1, int x2, int y2) {
        this.color = color;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    public void draw(FigureDrawContext fdc) {
        Rectangle r = fdc.toClientRectangle(x1, y1, x2, y2);
        fdc.gc.setBackground(color);
        fdc.gc.fillOval(r.x, r.y, r.width, r.height);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        region.add(fdc.toClientRectangle(x1, y1, x2, y2));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * The superclass for paint tools that contruct objects from individually
 * picked segments.
 */
abstract class SegmentedPaintSession extends BasicPaintSession {
    /**
     * The set of control points making up the segmented selection
     */
    private Vector /* of Point */ controlPoints = new Vector();

    /**
     * The previous figure (so that we can abort with right-button)
     */
    private Figure previousFigure = null;

    /**
     * The current figure (so that we can abort with right-button)
     */
    private Figure currentFigure = null;

    /**
     * Constructs a PaintSession.
     * 
     * @param paintSurface the drawing surface to use
     */
    protected SegmentedPaintSession(PaintSurface paintSurface) {
        super(paintSurface);
    }

    /**
     * Activates the tool.
     */
    public void beginSession() {
        getPaintSurface().setStatusMessage(
                PaintExample.getResourceString("session.SegmentedInteractivePaint.message.anchorMode"));
        previousFigure = null;
        currentFigure = null;
        controlPoints.clear();
    }

    /**
     * Deactivates the tool.
      */
    public void endSession() {
        getPaintSurface().clearRubberbandSelection();
        if (previousFigure != null)
            getPaintSurface().drawFigure(previousFigure);
    }

    /**
     * Resets the tool.
     * Aborts any operation in progress.
     */
    public void resetSession() {
        getPaintSurface().clearRubberbandSelection();
        if (previousFigure != null)
            getPaintSurface().drawFigure(previousFigure);

        getPaintSurface().setStatusMessage(
                PaintExample.getResourceString("session.SegmentedInteractivePaint.message.anchorMode"));
        previousFigure = null;
        currentFigure = null;
        controlPoints.clear();
    }

    /**
     * Handles a mouseDown event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseDown(MouseEvent event) {
        if (event.button != 1)
            return;

        getPaintSurface().setStatusMessage(
                PaintExample.getResourceString("session.SegmentedInteractivePaint.message.interactiveMode"));
        previousFigure = currentFigure;

        if (controlPoints.size() > 0) {
            final Point lastPoint = (Point) controlPoints.elementAt(controlPoints.size() - 1);
            if (lastPoint.x == event.x || lastPoint.y == event.y)
                return; // spurious event
        }
        controlPoints.add(new Point(event.x, event.y));
    }

    /**
     * Handles a mouseDoubleClick event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseDoubleClick(MouseEvent event) {
        if (event.button != 1)
            return;
        if (controlPoints.size() >= 2) {
            getPaintSurface().clearRubberbandSelection();
            previousFigure = createFigure((Point[]) controlPoints.toArray(new Point[controlPoints.size()]),
                    controlPoints.size(), true);
        }
        resetSession();
    }

    /**
     * Handles a mouseUp event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseUp(MouseEvent event) {
        if (event.button != 1) {
            resetSession(); // abort if right or middle mouse button pressed
            return;
        }
    }

    /**
     * Handles a mouseMove event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseMove(MouseEvent event) {
        final PaintSurface ps = getPaintSurface();
        if (controlPoints.size() == 0) {
            ps.setStatusCoord(ps.getCurrentPosition());
            return; // spurious event
        } else {
            ps.setStatusCoordRange((Point) controlPoints.elementAt(controlPoints.size() - 1),
                    ps.getCurrentPosition());
        }

        ps.clearRubberbandSelection();
        Point[] points = (Point[]) controlPoints.toArray(new Point[controlPoints.size() + 1]);
        points[controlPoints.size()] = ps.getCurrentPosition();
        currentFigure = createFigure(points, points.length, false);
        ps.addRubberbandSelection(currentFigure);
    }

    /**
     * Template Method: Creates a Figure for drawing rubberband entities and the final product
     * 
     * @param points the array of control points
     * @param numPoints the number of valid points in the array (n >= 2)
     * @param closed true if the user double-clicked on the final control point
     */
    protected abstract Figure createFigure(Point[] points, int numPoints, boolean closed);
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * A drawing tool.
 */
class RoundedRectangleTool extends DragPaintSession implements PaintTool {
    private ToolSettings settings;

    /**
     * Constructs a RoundedRectangleTool.
     * 
     * @param toolSettings the new tool settings
     * @param paintSurface the PaintSurface we will render on.
     */
    public RoundedRectangleTool(ToolSettings toolSettings, PaintSurface paintSurface) {
        super(paintSurface);
        set(toolSettings);
    }

    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings) {
        settings = toolSettings;
    }

    /**
     * Returns name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName() {
        return PaintExample.getResourceString("tool.RoundedRectangle.label");
    }

    /*
     * Template methods for drawing
     */
    protected Figure createFigure(Point a, Point b) {
        ContainerFigure container = new ContainerFigure();
        if (settings.commonFillType != ToolSettings.ftNone)
            container.add(new SolidRoundedRectangleFigure(settings.commonBackgroundColor, a.x, a.y, b.x, b.y,
                    settings.roundedRectangleCornerDiameter));
        if (settings.commonFillType != ToolSettings.ftSolid)
            container.add(new RoundedRectangleFigure(settings.commonForegroundColor, settings.commonBackgroundColor,
                    settings.commonLineStyle, a.x, a.y, b.x, b.y, settings.roundedRectangleCornerDiameter));
        return container;
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * 2D Rectangle object
 */
class RoundedRectangleFigure extends Figure {
    private Color foregroundColor, backgroundColor;
    private int lineStyle, x1, y1, x2, y2, diameter;

    /**
     * Constructs a Rectangle
     * These objects are defined by any two diametrically opposing corners.
     * 
     * @param color the color for this object
     * @param lineStyle the line style for this object
     * @param x1 the virtual X coordinate of the first corner
     * @param y1 the virtual Y coordinate of the first corner
     * @param x2 the virtual X coordinate of the second corner
     * @param y2 the virtual Y coordinate of the second corner
     * @param diameter the diameter of curvature of all four corners
     */
    public RoundedRectangleFigure(Color foregroundColor, Color backgroundColor, int lineStyle, int x1, int y1,
            int x2, int y2, int diameter) {
        this.foregroundColor = foregroundColor;
        this.backgroundColor = backgroundColor;
        this.lineStyle = lineStyle;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        this.diameter = diameter;
    }

    public void draw(FigureDrawContext fdc) {
        Rectangle r = fdc.toClientRectangle(x1, y1, x2, y2);
        fdc.gc.setForeground(foregroundColor);
        fdc.gc.setBackground(backgroundColor);
        fdc.gc.setLineStyle(lineStyle);
        fdc.gc.drawRoundRectangle(r.x, r.y, r.width - 1, r.height - 1, diameter, diameter);
        fdc.gc.setLineStyle(SWT.LINE_SOLID);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        region.add(fdc.toClientRectangle(x1, y1, x2, y2));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * A drawing tool.
 */
class RectangleTool extends DragPaintSession implements PaintTool {
    private ToolSettings settings;

    /**
     * Constructs a RectangleTool.
     * 
     * @param toolSettings the new tool settings
     * @param paintSurface the PaintSurface we will render on.
     */
    public RectangleTool(ToolSettings toolSettings, PaintSurface paintSurface) {
        super(paintSurface);
        set(toolSettings);
    }

    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings) {
        settings = toolSettings;
    }

    /**
     * Returns name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName() {
        return PaintExample.getResourceString("tool.Rectangle.label");
    }

    /*
     * Template method for drawing
     */
    protected Figure createFigure(Point a, Point b) {
        switch (settings.commonFillType) {
        default:
        case ToolSettings.ftNone:
            return new RectangleFigure(settings.commonForegroundColor, settings.commonBackgroundColor,
                    settings.commonLineStyle, a.x, a.y, b.x, b.y);
        case ToolSettings.ftSolid:
            return new SolidRectangleFigure(settings.commonBackgroundColor, a.x, a.y, b.x, b.y);
        case ToolSettings.ftOutline: {
            ContainerFigure container = new ContainerFigure();
            container.add(new SolidRectangleFigure(settings.commonBackgroundColor, a.x, a.y, b.x, b.y));
            container.add(new RectangleFigure(settings.commonForegroundColor, settings.commonBackgroundColor,
                    settings.commonLineStyle, a.x, a.y, b.x, b.y));
            return container;
        }
        }
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * 2D Rectangle object
 */
class RectangleFigure extends Figure {
    private Color foregroundColor, backgroundColor;
    private int lineStyle, x1, y1, x2, y2;

    /**
     * Constructs a Rectangle
     * These objects are defined by any two diametrically opposing corners.
     * 
     * @param color the color for this object
     * @param lineStyle the line style for this object
     * @param x1 the virtual X coordinate of the first corner
     * @param y1 the virtual Y coordinate of the first corner
     * @param x2 the virtual X coordinate of the second corner
     * @param y2 the virtual Y coordinate of the second corner
     */
    public RectangleFigure(Color foregroundColor, Color backgroundColor, int lineStyle, int x1, int y1, int x2,
            int y2) {
        this.foregroundColor = foregroundColor;
        this.backgroundColor = backgroundColor;
        this.lineStyle = lineStyle;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    public void draw(FigureDrawContext fdc) {
        Rectangle r = fdc.toClientRectangle(x1, y1, x2, y2);
        fdc.gc.setForeground(foregroundColor);
        fdc.gc.setBackground(backgroundColor);
        fdc.gc.setLineStyle(lineStyle);
        fdc.gc.drawRectangle(r.x, r.y, r.width - 1, r.height - 1);
        fdc.gc.setLineStyle(SWT.LINE_SOLID);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        region.add(fdc.toClientRectangle(x1, y1, x2, y2));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * A polyline drawing tool.
 */
class PolyLineTool extends SegmentedPaintSession implements PaintTool {
    private ToolSettings settings;

    /**
     * Constructs a PolyLineTool.
     * 
     * @param toolSettings the new tool settings
     * @param paintSurface the PaintSurface we will render on.
     */
    public PolyLineTool(ToolSettings toolSettings, PaintSurface paintSurface) {
        super(paintSurface);
        set(toolSettings);
    }

    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings) {
        settings = toolSettings;
    }

    /**
     * Returns the name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName() {
        return PaintExample.getResourceString("tool.PolyLine.label");
    }

    /*
     * Template methods for drawing
     */
    protected Figure createFigure(Point[] points, int numPoints, boolean closed) {
        ContainerFigure container = new ContainerFigure();
        if (closed && settings.commonFillType != ToolSettings.ftNone && numPoints >= 3) {
            container.add(new SolidPolygonFigure(settings.commonBackgroundColor, points, numPoints));
        }
        if (!closed || settings.commonFillType != ToolSettings.ftSolid || numPoints < 3) {
            for (int i = 0; i < numPoints - 1; ++i) {
                final Point a = points[i];
                final Point b = points[i + 1];
                container.add(new LineFigure(settings.commonForegroundColor, settings.commonBackgroundColor,
                        settings.commonLineStyle, a.x, a.y, b.x, b.y));
            }
            if (closed) {
                final Point a = points[points.length - 1];
                final Point b = points[0];
                container.add(new LineFigure(settings.commonForegroundColor, settings.commonBackgroundColor,
                        settings.commonLineStyle, a.x, a.y, b.x, b.y));
            }
        }
        return container;
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * 2D Point object
 */
class PointFigure extends Figure {
    private Color color;
    private int x, y;

    /**
     * Constructs a Point
     * 
     * @param color the color for this object
     * @param x the virtual X coordinate of the first end-point
     * @param y the virtual Y coordinate of the first end-point
     */
    public PointFigure(Color color, int x, int y) {
        this.color = color;
        this.x = x;
        this.y = y;
    }

    public void draw(FigureDrawContext fdc) {
        Point p = fdc.toClientPoint(x, y);
        fdc.gc.setBackground(color);
        fdc.gc.fillRectangle(p.x, p.y, 1, 1);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        region.add(fdc.toClientRectangle(x, y, x, y));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * A pencil tool.
 */
class PencilTool extends ContinuousPaintSession implements PaintTool {
    private ToolSettings settings;

    /**
     * Constructs a pencil tool.
     * 
     * @param toolSettings the new tool settings
     * @param getPaintSurface() the PaintSurface we will render on.
     */
    public PencilTool(ToolSettings toolSettings, PaintSurface paintSurface) {
        super(paintSurface);
        set(toolSettings);
    }

    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings) {
        settings = toolSettings;
    }

    /**
     * Returns the name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName() {
        return PaintExample.getResourceString("tool.Pencil.label");
    }

    /*
     * Template method for drawing
     */
    public void render(final Point point) {
        final PaintSurface ps = getPaintSurface();
        ps.drawFigure(new PointFigure(settings.commonForegroundColor, point.x, point.y));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
interface PaintTool extends PaintSession {
    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings);
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * Manages a simple drawing surface.
 */
class PaintSurface {
    private Point currentPosition = new Point(0, 0);
    private Canvas paintCanvas;

    private PaintSession paintSession;
    private Image image;
    private Image paintImage; // buffer for refresh blits
    private int imageWidth, imageHeight;
    private int visibleWidth, visibleHeight;

    private FigureDrawContext displayFDC = new FigureDrawContext();
    private FigureDrawContext imageFDC = new FigureDrawContext();
    private FigureDrawContext paintFDC = new FigureDrawContext();

    /* Rubberband */
    private ContainerFigure rubberband = new ContainerFigure();
    // the active rubberband selection
    private int rubberbandHiddenNestingCount = 0;
    // always >= 0, if > 0 rubberband has been hidden

    /* Status */
    private Text statusText;
    private String statusActionInfo, statusMessageInfo, statusCoordInfo;

    /**
     * Constructs a PaintSurface.
     * <p>
     * paintCanvas must have SWT.NO_REDRAW_RESIZE and SWT.NO_BACKGROUND styles,
     *     and may have SWT.V_SCROLL and/or SWT.H_SCROLL.
     * </p>
     * @param paintCanvas the Canvas object in which to render
     * @param paintStatus the PaintStatus object to use for providing user feedback
     * @param fillColor the color to fill the canvas with initially
     */
    public PaintSurface(Canvas paintCanvas, Text statusText, Color fillColor) {
        this.paintCanvas = paintCanvas;
        this.statusText = statusText;
        clearStatus();

        /* Set up the drawing surface */
        Rectangle displayRect = paintCanvas.getDisplay().getClientArea();
        imageWidth = displayRect.width;
        imageHeight = displayRect.height;
        image = new Image(paintCanvas.getDisplay(), imageWidth, imageHeight);

        imageFDC.gc = new GC(image);
        imageFDC.gc.setBackground(fillColor);
        imageFDC.gc.fillRectangle(0, 0, imageWidth, imageHeight);
        displayFDC.gc = new GC(paintCanvas);

        /* Initialize the session */
        setPaintSession(null);

        /* Add our listeners */
        paintCanvas.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                displayFDC.gc.dispose();
            }
        });
        paintCanvas.addMouseListener(new MouseAdapter() {
            public void mouseDown(MouseEvent event) {
                processMouseEventCoordinates(event);
                if (paintSession != null)
                    paintSession.mouseDown(event);
            }

            public void mouseUp(MouseEvent event) {
                processMouseEventCoordinates(event);
                if (paintSession != null)
                    paintSession.mouseUp(event);
            }

            public void mouseDoubleClick(MouseEvent event) {
                processMouseEventCoordinates(event);
                if (paintSession != null)
                    paintSession.mouseDoubleClick(event);
            }
        });
        paintCanvas.addMouseMoveListener(new MouseMoveListener() {
            public void mouseMove(MouseEvent event) {
                processMouseEventCoordinates(event);
                if (paintSession != null)
                    paintSession.mouseMove(event);
            }
        });
        paintCanvas.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent event) {
                if (rubberband.isEmpty()) {
                    // Nothing to merge, so we just refresh
                    event.gc.drawImage(image, displayFDC.xOffset + event.x, displayFDC.yOffset + event.y,
                            event.width, event.height, event.x, event.y, event.width, event.height);
                } else {
                    /*
                     * Avoid flicker when merging overlayed objects by constructing the image on
                     * a backbuffer first, then blitting it to the screen.
                     */
                    // Check that the backbuffer is large enough
                    if (paintImage != null) {
                        Rectangle rect = paintImage.getBounds();
                        if ((event.width + event.x > rect.width) || (event.height + event.y > rect.height)) {
                            paintFDC.gc.dispose();
                            paintImage.dispose();
                            paintImage = null;
                        }
                    }
                    if (paintImage == null) {
                        Display display = getDisplay();
                        Rectangle rect = display.getClientArea();
                        paintImage = new Image(display, Math.max(rect.width, event.width + event.x),
                                Math.max(rect.height, event.height + event.y));
                        paintFDC.gc = new GC(paintImage);
                    }
                    // Setup clipping and the FDC
                    Region clipRegion = new Region();
                    event.gc.getClipping(clipRegion);
                    paintFDC.gc.setClipping(clipRegion);
                    clipRegion.dispose();

                    paintFDC.xOffset = displayFDC.xOffset;
                    paintFDC.yOffset = displayFDC.yOffset;
                    paintFDC.xScale = displayFDC.xScale;
                    paintFDC.yScale = displayFDC.yScale;

                    // Merge the overlayed objects into the image, then blit
                    paintFDC.gc.drawImage(image, displayFDC.xOffset + event.x, displayFDC.yOffset + event.y,
                            event.width, event.height, event.x, event.y, event.width, event.height);
                    rubberband.draw(paintFDC);
                    event.gc.drawImage(paintImage, event.x, event.y, event.width, event.height, event.x, event.y,
                            event.width, event.height);
                }
            }
        });
        paintCanvas.addControlListener(new ControlAdapter() {
            public void controlResized(ControlEvent event) {
                handleResize();
            }
        });

        /* Set up the paint canvas scroll bars */
        ScrollBar horizontal = paintCanvas.getHorizontalBar();
        horizontal.setVisible(true);
        horizontal.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                scrollHorizontally((ScrollBar) event.widget);
            }
        });
        ScrollBar vertical = paintCanvas.getVerticalBar();
        vertical.setVisible(true);
        vertical.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                scrollVertically((ScrollBar) event.widget);
            }
        });
        handleResize();
    }

    /**
     * Disposes of the PaintSurface's resources.
     */
    public void dispose() {
        imageFDC.gc.dispose();
        image.dispose();
        if (paintImage != null) {
            paintImage.dispose();
            paintFDC.gc.dispose();
        }

        currentPosition = null;
        paintCanvas = null;
        paintSession = null;
        image = null;
        paintImage = null;
        displayFDC = null;
        imageFDC = null;
        paintFDC = null;
        rubberband = null;
        statusText = null;
        statusActionInfo = null;
        statusMessageInfo = null;
        statusCoordInfo = null;
    }

    /**
     * Called when we must grab focus.
     */
    public void setFocus() {
        paintCanvas.setFocus();
    }

    /**
     * Returns the Display on which the PaintSurface resides.
     * @return the Display
     */
    public Display getDisplay() {
        return paintCanvas.getDisplay();
    }

    /**
     * Returns the Shell in which the PaintSurface resides.
     * @return the Shell
     */
    public Shell getShell() {
        return paintCanvas.getShell();
    }

    /**
     * Sets the current paint session.
     * <p>
     * If oldPaintSession != paintSession calls oldPaintSession.end()
     * and paintSession.begin()
     * </p>
     * 
     * @param paintSession the paint session to activate; null to disable all sessions
     */
    public void setPaintSession(PaintSession paintSession) {
        if (this.paintSession != null) {
            if (this.paintSession == paintSession)
                return;
            this.paintSession.endSession();
        }
        this.paintSession = paintSession;
        clearStatus();
        if (paintSession != null) {
            setStatusAction(paintSession.getDisplayName());
            paintSession.beginSession();
        } else {
            setStatusAction(PaintExample.getResourceString("tool.Null.label"));
            setStatusMessage(PaintExample.getResourceString("session.Null.message"));
        }
    }

    /**
     * Returns the current paint session.
     * 
     * @return the current paint session, null if none is active
     */
    public PaintSession getPaintSession() {
        return paintSession;
    }

    /**
     * Returns the current paint tool.
     * 
     * @return the current paint tool, null if none is active (though some other session
     *         might be)
     */
    public PaintTool getPaintTool() {
        return (paintSession != null && paintSession instanceof PaintTool) ? (PaintTool) paintSession : null;
    }

    /**
     * Returns the current position in an interactive operation.
     *
     * @return the last known position of the pointer
     */
    public Point getCurrentPosition() {
        return currentPosition;
    }

    /**
     * Draws a Figure object to the screen and to the backing store permanently.
     * 
     * @param object the object to draw onscreen
     */
    public void drawFigure(Figure object) {
        object.draw(imageFDC);
        object.draw(displayFDC);
    }

    /**
     * Adds a Figure object to the active rubberband selection.
     * <p>
     * This object will be drawn to the screen as a preview and refreshed appropriately
     * until the selection is either cleared or committed.
     * </p>
     * 
     * @param object the object to add to the selection
     */
    public void addRubberbandSelection(Figure object) {
        rubberband.add(object);
        if (!isRubberbandHidden())
            object.draw(displayFDC);
    }

    /**
     * Clears the active rubberband selection.
     * <p>
     * Erases any rubberband objects on the screen then clears the selection.
     * </p>
     */
    public void clearRubberbandSelection() {
        if (!isRubberbandHidden()) {
            Region region = new Region();
            rubberband.addDamagedRegion(displayFDC, region);
            Rectangle r = region.getBounds();
            paintCanvas.redraw(r.x, r.y, r.width, r.height, true);
            region.dispose();
        }
        rubberband.clear();

    }

    /**
     * Commits the active rubberband selection.
     * <p>
     * Redraws any rubberband objects on the screen as permanent objects then clears the selection.
     * </p>
     */
    public void commitRubberbandSelection() {
        rubberband.draw(imageFDC);
        if (isRubberbandHidden())
            rubberband.draw(displayFDC);
        rubberband.clear();
    }

    /**
     * Hides the rubberband (but does not eliminate it).
     * <p>
     * Increments by one the rubberband "hide" nesting count.  The rubberband
     * is hidden from view (but remains active) if it wasn't already hidden.
     * </p>
     */
    public void hideRubberband() {
        if (rubberbandHiddenNestingCount++ <= 0) {
            Region region = new Region();
            rubberband.addDamagedRegion(displayFDC, region);
            Rectangle r = region.getBounds();
            paintCanvas.redraw(r.x, r.y, r.width, r.height, true);
            region.dispose();
        }
    }

    /**
     * Shows (un-hides) the rubberband.
     * <p>
     * Decrements by one the rubberband "hide" nesting count.  The rubberband
     * is only made visible when showRubberband() has been called once for each
     * previous hideRubberband().  It is not permitted to call showRubberband() if
     * the rubber band is not presently hidden.
     * </p>
     */
    public void showRubberband() {
        if (rubberbandHiddenNestingCount <= 0)
            throw new IllegalStateException("rubberbandHiddenNestingCount > 0");
        if (--rubberbandHiddenNestingCount == 0) {
            rubberband.draw(displayFDC);
        }
    }

    /**
     * Determines if the rubberband is hidden.
     * 
     * @return true iff the rubber is hidden
     */
    public boolean isRubberbandHidden() {
        return rubberbandHiddenNestingCount > 0;
    }

    /**
     * Handles a horizontal scroll event
     * 
     * @param scrollbar the horizontal scroll bar that posted this event
     */
    public void scrollHorizontally(ScrollBar scrollBar) {
        if (image == null)
            return;
        if (imageWidth > visibleWidth) {
            final int oldOffset = displayFDC.xOffset;
            final int newOffset = Math.min(scrollBar.getSelection(), imageWidth - visibleWidth);
            if (oldOffset != newOffset) {
                paintCanvas.update();
                displayFDC.xOffset = newOffset;
                paintCanvas.scroll(Math.max(oldOffset - newOffset, 0), 0, Math.max(newOffset - oldOffset, 0), 0,
                        visibleWidth, visibleHeight, false);
            }
        }
    }

    /**
     * Handles a vertical scroll event
     * 
     * @param scrollbar the vertical scroll bar that posted this event
     */
    public void scrollVertically(ScrollBar scrollBar) {
        if (image == null)
            return;
        if (imageHeight > visibleHeight) {
            final int oldOffset = displayFDC.yOffset;
            final int newOffset = Math.min(scrollBar.getSelection(), imageHeight - visibleHeight);
            if (oldOffset != newOffset) {
                paintCanvas.update();
                displayFDC.yOffset = newOffset;
                paintCanvas.scroll(0, Math.max(oldOffset - newOffset, 0), 0, Math.max(newOffset - oldOffset, 0),
                        visibleWidth, visibleHeight, false);
            }
        }
    }

    /**
     * Handles resize events
     */
    private void handleResize() {
        paintCanvas.update();

        Rectangle visibleRect = paintCanvas.getClientArea();
        visibleWidth = visibleRect.width;
        visibleHeight = visibleRect.height;

        ScrollBar horizontal = paintCanvas.getHorizontalBar();
        if (horizontal != null) {
            displayFDC.xOffset = Math.min(horizontal.getSelection(), imageWidth - visibleWidth);
            if (imageWidth <= visibleWidth) {
                horizontal.setEnabled(false);
                horizontal.setSelection(0);
            } else {
                horizontal.setEnabled(true);
                horizontal.setValues(displayFDC.xOffset, 0, imageWidth, visibleWidth, 8, visibleWidth);
            }
        }

        ScrollBar vertical = paintCanvas.getVerticalBar();
        if (vertical != null) {
            displayFDC.yOffset = Math.min(vertical.getSelection(), imageHeight - visibleHeight);
            if (imageHeight <= visibleHeight) {
                vertical.setEnabled(false);
                vertical.setSelection(0);
            } else {
                vertical.setEnabled(true);
                vertical.setValues(displayFDC.yOffset, 0, imageHeight, visibleHeight, 8, visibleHeight);
            }
        }
    }

    /**
     * Virtualizes MouseEvent coordinates and stores the current position.
     */
    private void processMouseEventCoordinates(MouseEvent event) {
        currentPosition.x = event.x = Math.min(Math.max(event.x, 0), visibleWidth - 1) + displayFDC.xOffset;
        currentPosition.y = event.y = Math.min(Math.max(event.y, 0), visibleHeight - 1) + displayFDC.yOffset;
    }

    /**
     * Clears the status bar.
     */
    public void clearStatus() {
        statusActionInfo = "";
        statusMessageInfo = "";
        statusCoordInfo = "";
        updateStatus();
    }

    /**
     * Sets the status bar action text.
     *
     * @param action the action in progress, null to clear
     */
    public void setStatusAction(String action) {
        statusActionInfo = (action != null) ? action : "";
        updateStatus();
    }

    /**
     * Sets the status bar message text.
     * 
     * @param message the message to display, null to clear
     */
    public void setStatusMessage(String message) {
        statusMessageInfo = (message != null) ? message : "";
        updateStatus();
    }

    /**
     * Sets the coordinates in the status bar.
     * 
     * @param coord the coordinates to display, null to clear
     */
    public void setStatusCoord(Point coord) {
        statusCoordInfo = (coord != null) ? PaintExample.getResourceString("status.Coord.format",
                new Object[] { new Integer(coord.x), new Integer(coord.y) }) : "";
        updateStatus();
    }

    /**
     * Sets the coordinate range in the status bar.
     * 
     * @param a the "from" coordinate, must not be null
     * @param b the "to" coordinate, must not be null
     */
    public void setStatusCoordRange(Point a, Point b) {
        statusCoordInfo = PaintExample.getResourceString("status.CoordRange.format",
                new Object[] { new Integer(a.x), new Integer(a.y), new Integer(b.x), new Integer(b.y) });
        updateStatus();
    }

    /**
     * Updates the display.
     */
    private void updateStatus() {
        statusText.setText(PaintExample.getResourceString("status.Bar.format",
                new Object[] { statusActionInfo, statusMessageInfo, statusCoordInfo }));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * Manages an interactive paint session.
 * Note that the coordinates received via the listener interfaces are virtualized to zero-origin
 * relative to the painting surface.
 */
interface PaintSession extends MouseListener, MouseMoveListener {
    /**
     * Returns the paint surface associated with this paint session
     * 
     * @return the associated PaintSurface
     */
    public PaintSurface getPaintSurface();

    /**
     * Activates the session.
     * 
     * Note: When overriding this method, call super.beginSession() at method start.
     */
    public abstract void beginSession();

    /**
     * Deactivates the session.
      *
     * Note: When overriding this method, call super.endSession() at method exit.
      */
    public abstract void endSession();

    /**
     * Resets the session.
     * Aborts any operation in progress.
     * 
     * Note: When overriding this method, call super.resetSession() at method exit.
     */
    public abstract void resetSession();

    /**
     * Returns the name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName();
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * A line drawing tool
 */
class LineTool extends DragPaintSession implements PaintTool {
    private ToolSettings settings;

    /**
     * Constructs a LineTool.
     * 
     * @param toolSettings the new tool settings
     * @param paintSurface the PaintSurface we will render on.
     */
    public LineTool(ToolSettings toolSettings, PaintSurface paintSurface) {
        super(paintSurface);
        set(toolSettings);
    }

    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings) {
        settings = toolSettings;
    }

    /**
     * Returns name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName() {
        return PaintExample.getResourceString("tool.Line.label");
    }

    /*
     * Template methods for drawing
     */
    protected Figure createFigure(Point a, Point b) {
        return new LineFigure(settings.commonForegroundColor, settings.commonBackgroundColor,
                settings.commonLineStyle, a.x, a.y, b.x, b.y);
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * 2D Line object
 */
class LineFigure extends Figure {
    private Color foregroundColor, backgroundColor;
    private int lineStyle, x1, y1, x2, y2;

    /**
     * Constructs a Line
     * These objects are defined by their two end-points.
     * 
     * @param color the color for this object
     * @param lineStyle the line style for this object
     * @param x1 the virtual X coordinate of the first end-point
     * @param y1 the virtual Y coordinate of the first end-point
     * @param x2 the virtual X coordinate of the second end-point
     * @param y2 the virtual Y coordinate of the second end-point
     */
    public LineFigure(Color foregroundColor, Color backgroundColor, int lineStyle, int x1, int y1, int x2, int y2) {
        this.foregroundColor = foregroundColor;
        this.backgroundColor = backgroundColor;
        this.lineStyle = lineStyle;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    public void draw(FigureDrawContext fdc) {
        Point p1 = fdc.toClientPoint(x1, y1);
        Point p2 = fdc.toClientPoint(x2, y2);
        fdc.gc.setForeground(foregroundColor);
        fdc.gc.setBackground(backgroundColor);
        fdc.gc.setLineStyle(lineStyle);
        fdc.gc.drawLine(p1.x, p1.y, p2.x, p2.y);
        fdc.gc.setLineStyle(SWT.LINE_SOLID);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        region.add(fdc.toClientRectangle(x1, y1, x2, y2));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
class FigureDrawContext {
    /*
     * <p>
     * The GC must be set up as follows
     * (it will be returned to this state upon completion of drawing operations)
     * <ul>
     *   <li>setXORMode(false)
     * </ul>
     * </p>
     */
    public GC gc = null;
    public int xOffset = 0, yOffset = 0; // substract to get GC coords
    public int xScale = 1, yScale = 1;

    public Rectangle toClientRectangle(int x1, int y1, int x2, int y2) {
        return new Rectangle(Math.min(x1, x2) * xScale - xOffset, Math.min(y1, y2) * yScale - yOffset,
                (Math.abs(x2 - x1) + 1) * xScale, (Math.abs(y2 - y1) + 1) * yScale);
    }

    public Point toClientPoint(int x, int y) {
        return new Point(x * xScale - xOffset, y * yScale - yOffset);
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * Superinterface for all drawing objects.
 * All drawing objects know how to render themselved to the screen and can draw a
 * temporary version of themselves for previewing the general appearance of the
 * object onscreen before it gets committed.
 */
abstract class Figure {
    /**
     * Draws this object.
     * 
     * @param fdc a parameter block specifying drawing-related information
     */
    public abstract void draw(FigureDrawContext fdc);

    /**
     * Computes the damaged screen region caused by drawing this object (imprecise), then
     * appends it to the supplied region.
     * 
     * @param fdc a parameter block specifying drawing-related information
     * @param region a region to which additional damage areas will be added
     */
    public abstract void addDamagedRegion(FigureDrawContext fdc, Region region);
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * A drawing tool.
 */
class EllipseTool extends DragPaintSession implements PaintTool {
    private ToolSettings settings;

    /**
     * Constructs a EllipseTool.
     * 
     * @param toolSettings the new tool settings
     * @param paintSurface the PaintSurface we will render on.
     */
    public EllipseTool(ToolSettings toolSettings, PaintSurface paintSurface) {
        super(paintSurface);
        set(toolSettings);
    }

    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings) {
        settings = toolSettings;
    }

    /**
     * Returns name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName() {
        return PaintExample.getResourceString("tool.Ellipse.label");
    }

    /*
     * Template methods for drawing
     */
    protected Figure createFigure(Point a, Point b) {
        ContainerFigure container = new ContainerFigure();
        if (settings.commonFillType != ToolSettings.ftNone)
            container.add(new SolidEllipseFigure(settings.commonBackgroundColor, a.x, a.y, b.x, b.y));
        if (settings.commonFillType != ToolSettings.ftSolid)
            container.add(new EllipseFigure(settings.commonForegroundColor, settings.commonBackgroundColor,
                    settings.commonLineStyle, a.x, a.y, b.x, b.y));
        return container;
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * 2D Ellipse object
 */
class EllipseFigure extends Figure {
    private Color foregroundColor, backgroundColor;
    private int lineStyle, x1, y1, x2, y2;

    /**
     * Constructs an Ellipse
     * These objects are defined by any two diametrically opposing corners of a box
     * bounding the ellipse.
     * 
     * @param color the color for this object
     * @param lineStyle the line style for this object
     * @param x1 the virtual X coordinate of the first corner
     * @param y1 the virtual Y coordinate of the first corner
     * @param x2 the virtual X coordinate of the second corner
     * @param y2 the virtual Y coordinate of the second corner
     */
    public EllipseFigure(Color foregroundColor, Color backgroundColor, int lineStyle, int x1, int y1, int x2,
            int y2) {
        this.foregroundColor = foregroundColor;
        this.backgroundColor = backgroundColor;
        this.lineStyle = lineStyle;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    public void draw(FigureDrawContext fdc) {
        Rectangle r = fdc.toClientRectangle(x1, y1, x2, y2);
        fdc.gc.setForeground(foregroundColor);
        fdc.gc.setBackground(backgroundColor);
        fdc.gc.setLineStyle(lineStyle);
        fdc.gc.drawOval(r.x, r.y, r.width - 1, r.height - 1);
        fdc.gc.setLineStyle(SWT.LINE_SOLID);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        region.add(fdc.toClientRectangle(x1, y1, x2, y2));
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * The superclass for paint tools that use click-drag-release motions to
 * draw objects.
 */
abstract class DragPaintSession extends BasicPaintSession {
    /**
     * True if a click-drag is in progress
     */
    private boolean dragInProgress = false;

    /**
     * The position of the first click in a click-drag
     */
    private Point anchorPosition = new Point(-1, -1);

    /**
     * A temporary point
     */
    private Point tempPosition = new Point(-1, -1);

    /**
     * Constructs a PaintSession.
     * 
     * @param getPaintSurface() the drawing surface to use
     */
    protected DragPaintSession(PaintSurface paintSurface) {
        super(paintSurface);
    }

    /**
     * Activates the tool.
     */
    public void beginSession() {
        getPaintSurface().setStatusMessage(PaintExample.getResourceString("session.DragInteractivePaint.message"));
        anchorPosition.x = -1;
        dragInProgress = false;
    }

    /**
     * Deactivates the tool.
      */
    public void endSession() {
    }

    /**
     * Resets the tool.
     * Aborts any operation in progress.
     */
    public void resetSession() {
        getPaintSurface().clearRubberbandSelection();
        anchorPosition.x = -1;
        dragInProgress = false;
    }

    /**
     * Handles a mouseDown event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseDown(MouseEvent event) {
        if (event.button != 1)
            return;
        if (dragInProgress)
            return; // spurious event
        dragInProgress = true;

        anchorPosition.x = event.x;
        anchorPosition.y = event.y;
    }

    /**
     * Handles a mouseDoubleClick event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseDoubleClick(MouseEvent event) {
    }

    /**
     * Handles a mouseUp event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseUp(MouseEvent event) {
        if (event.button != 1) {
            resetSession(); // abort if right or middle mouse button pressed
            return;
        }
        if (!dragInProgress)
            return; // spurious event
        dragInProgress = false;
        if (anchorPosition.x == -1)
            return; // spurious event

        getPaintSurface().commitRubberbandSelection();
    }

    /**
     * Handles a mouseMove event.
     * 
     * @param event the mouse event detail information
     */
    public void mouseMove(MouseEvent event) {
        final PaintSurface ps = getPaintSurface();
        if (!dragInProgress) {
            ps.setStatusCoord(ps.getCurrentPosition());
            return;
        }
        ps.setStatusCoordRange(anchorPosition, ps.getCurrentPosition());
        ps.clearRubberbandSelection();
        tempPosition.x = event.x;
        tempPosition.y = event.y;
        ps.addRubberbandSelection(createFigure(anchorPosition, tempPosition));
    }

    /**
     * Template Method: Creates a Figure for drawing rubberband entities and the final product
     * 
     * @param anchor the anchor point
     * @param cursor the point marking the current pointer location
     */
    protected abstract Figure createFigure(Point anchor, Point cursor);
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * The superclass for paint tools that draw continuously along the path
 * traced by the mouse's movement while the button is depressed
 */
abstract class ContinuousPaintSession extends BasicPaintSession {
    /**
     * True if a click-drag is in progress.
     */
    private boolean dragInProgress = false;

    /**
     * A cached Point array for drawing.
     */
    private Point[] points = new Point[] { new Point(-1, -1), new Point(-1, -1) };

    /**
     * The time to wait between retriggers in milliseconds.
     */
    private int retriggerInterval = 0;

    /**
     * The currently valid RetriggerHandler
     */
    protected Runnable retriggerHandler = null;

    /**
     * Constructs a ContinuousPaintSession.
     * 
     * @param paintSurface the drawing surface to use
     */
    protected ContinuousPaintSession(PaintSurface paintSurface) {
        super(paintSurface);
    }

    /**
     * Sets the retrigger timer.
     * <p>
     * After the timer elapses, if the mouse is still hovering over the same point with the
     * drag button pressed, a new render order is issued and the timer is restarted.
     * </p>
     * @param interval the time in milliseconds to wait between retriggers, 0 to disable
     */
    public void setRetriggerTimer(int interval) {
        retriggerInterval = interval;
    }

    /**
     * Activates the tool.
     */
    public void beginSession() {
        getPaintSurface().setStatusMessage(PaintExample.getResourceString("session.ContinuousPaint.message"));
        dragInProgress = false;
    }

    /**
     * Deactivates the tool.
      */
    public void endSession() {
        abortRetrigger();
    }

    /**
     * Aborts the current operation.
     */
    public void resetSession() {
        abortRetrigger();
    }

    /**
     * Handles a mouseDown event.
     * 
     * @param event the mouse event detail information
     */
    public final void mouseDown(MouseEvent event) {
        if (event.button != 1)
            return;
        if (dragInProgress)
            return; // spurious event
        dragInProgress = true;

        points[0].x = event.x;
        points[0].y = event.y;
        render(points[0]);
        prepareRetrigger();
    }

    /**
     * Handles a mouseDoubleClick event.
     * 
     * @param event the mouse event detail information
     */
    public final void mouseDoubleClick(MouseEvent event) {
    }

    /**
     * Handles a mouseUp event.
     * 
     * @param event the mouse event detail information
     */
    public final void mouseUp(MouseEvent event) {
        if (event.button != 1)
            return;
        if (!dragInProgress)
            return; // spurious event
        abortRetrigger();
        mouseSegmentFinished(event);
        dragInProgress = false;
    }

    /**
     * Handles a mouseMove event.
     * 
     * @param event the mouse event detail information
     */
    public final void mouseMove(MouseEvent event) {
        final PaintSurface ps = getPaintSurface();
        ps.setStatusCoord(ps.getCurrentPosition());
        if (!dragInProgress)
            return;
        mouseSegmentFinished(event);
        prepareRetrigger();
    }

    /**
     * Handle a rendering segment
     * 
     * @param event the mouse event detail information
     */
    private final void mouseSegmentFinished(MouseEvent event) {
        if (points[0].x == -1)
            return; // spurious event
        if (points[0].x != event.x || points[0].y != event.y) {
            // draw new segment
            points[1].x = event.x;
            points[1].y = event.y;
            renderContinuousSegment();
        }
    }

    /**
     * Draws a continuous segment from points[0] to points[1].
     * Assumes points[0] has been drawn already.
     * 
     * @post points[0] will refer to the same point as points[1]
     */
    protected void renderContinuousSegment() {
        /* A lazy but effective line drawing algorithm */
        final int dX = points[1].x - points[0].x;
        final int dY = points[1].y - points[0].y;
        int absdX = Math.abs(dX);
        int absdY = Math.abs(dY);

        if ((dX == 0) && (dY == 0))
            return;

        if (absdY > absdX) {
            final int incfpX = (dX << 16) / absdY;
            final int incY = (dY > 0) ? 1 : -1;
            int fpX = points[0].x << 16; // X in fixedpoint format

            while (--absdY >= 0) {
                points[0].y += incY;
                points[0].x = (fpX += incfpX) >> 16;
                render(points[0]);
            }
            if (points[0].x == points[1].x)
                return;
            points[0].x = points[1].x;
        } else {
            final int incfpY = (dY << 16) / absdX;
            final int incX = (dX > 0) ? 1 : -1;
            int fpY = points[0].y << 16; // Y in fixedpoint format

            while (--absdX >= 0) {
                points[0].x += incX;
                points[0].y = (fpY += incfpY) >> 16;
                render(points[0]);
            }
            if (points[0].y == points[1].y)
                return;
            points[0].y = points[1].y;
        }
        render(points[0]);
    }

    /**
     * Prepare the retrigger timer
     */
    private final void prepareRetrigger() {
        if (retriggerInterval > 0) {
            /*
             * timerExec() provides a lightweight mechanism for running code at intervals from within
             * the event loop when timing accuracy is not important.
             *
             * Since it is not possible to cancel a timerExec(), we remember the Runnable that is
             * active in order to distinguish the valid one from the stale ones.  In practice,
             * if the interval is 1/100th of a second, then creating a few hundred new RetriggerHandlers
             * each second will not cause a significant performance hit.
             */
            Display display = getPaintSurface().getDisplay();
            retriggerHandler = new Runnable() {
                public void run() {
                    if (retriggerHandler == this) {
                        render(points[0]);
                        prepareRetrigger();
                    }
                }
            };
            display.timerExec(retriggerInterval, retriggerHandler);
        }
    }

    /**
     * Aborts the retrigger timer
     */
    private final void abortRetrigger() {
        retriggerHandler = null;
    }

    /**
     * Template method: Renders a point.
     * @param point, the point to render
     */
    protected abstract void render(Point point);
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
/**
 * Container for Figure objects with stacking preview mechanism.
 */
class ContainerFigure extends Figure {
    private static final int INITIAL_ARRAY_SIZE = 16;

    Figure[] objectStack = null;
    int nextIndex = 0;

    /**
     * Constructs an empty Container
     */
    public ContainerFigure() {
    }

    /**
     * Adds an object to the container for later drawing.
     * 
     * @param object the object to add to the drawing list
     */
    public void add(Figure object) {
        if (objectStack == null) {
            objectStack = new Figure[INITIAL_ARRAY_SIZE];
        } else if (objectStack.length <= nextIndex) {
            Figure[] newObjectStack = new Figure[objectStack.length * 2];
            System.arraycopy(objectStack, 0, newObjectStack, 0, objectStack.length);
            objectStack = newObjectStack;
        }
        objectStack[nextIndex] = object;
        ++nextIndex;
    }

    /**
     * Determines if the container is empty.
     * @return true if the container is empty
     */
    public boolean isEmpty() {
        return nextIndex == 0;
    }

    /**
     * Adds an object to the container and draws its preview then updates the supplied preview state.
     * 
     * @param object the object to add to the drawing list
     * @param gc the GC to draw on
     * @param offset the offset to add to virtual coordinates to get display coordinates
     * @param rememberedState the state returned by a previous drawPreview() or addAndPreview()
     *        using this Container, may be null if there was no such previous call
     * @return object state that must be passed to erasePreview() later to erase this object
     */
    //   public Object addAndPreview(Figure object, GC gc, Point offset, Object rememberedState) {
    //      Object[] stateStack = (Object[]) rememberedState;
    //      if (stateStack == null) {
    //         stateStack = new Object[INITIAL_ARRAY_SIZE];
    //      } else if (stateStack.length <= nextIndex) {
    //         Object[] newStateStack = new Object[stateStack.length * 2];
    //         System.arraycopy(stateStack, 0, newStateStack, 0, stateStack.length);
    //         stateStack = newStateStack;
    //      }
    //      add(object);
    //      stateStack[nextIndex - 1] = object.drawPreview(gc, offset);
    //      return stateStack;
    //   }
    /**
     * Clears the container.
     * <p>
     * Note that erasePreview() cannot be called after this point to erase any previous
     * drawPreview()'s.
     * </p>
     */
    public void clear() {
        while (--nextIndex > 0)
            objectStack[nextIndex] = null;
        nextIndex = 0;
    }

    public void draw(FigureDrawContext fdc) {
        for (int i = 0; i < nextIndex; ++i)
            objectStack[i].draw(fdc);
    }

    public void addDamagedRegion(FigureDrawContext fdc, Region region) {
        for (int i = 0; i < nextIndex; ++i)
            objectStack[i].addDamagedRegion(fdc, region);
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
abstract class BasicPaintSession implements PaintSession {
    /**
     * The paint surface
     */
    private PaintSurface paintSurface;

    /**
     * Constructs a PaintSession.
     * 
     * @param paintSurface the drawing surface to use
     */
    protected BasicPaintSession(PaintSurface paintSurface) {
        this.paintSurface = paintSurface;
    }

    /**
     * Returns the paint surface associated with this paint session.
     * 
     * @return the associated PaintSurface
     */
    public PaintSurface getPaintSurface() {
        return paintSurface;
    }
}

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

/**
 * An airbrush tool.
 */
class AirbrushTool extends ContinuousPaintSession implements PaintTool {
    private ToolSettings settings;
    private Random random;
    private int cachedRadiusSquared;
    private int cachedNumPoints;

    /**
     * Constructs a Tool.
     * 
     * @param toolSettings the new tool settings
     * @param paintSurface the PaintSurface we will render on.
     */
    public AirbrushTool(ToolSettings toolSettings, PaintSurface paintSurface) {
        super(paintSurface);
        random = new Random();
        setRetriggerTimer(10);
        set(toolSettings);
    }

    /**
     * Sets the tool's settings.
     * 
     * @param toolSettings the new tool settings
     */
    public void set(ToolSettings toolSettings) {
        // compute things we need to know for drawing
        settings = toolSettings;
        cachedRadiusSquared = settings.airbrushRadius * settings.airbrushRadius;
        cachedNumPoints = 314 * settings.airbrushIntensity * cachedRadiusSquared / 250000;
        if (cachedNumPoints == 0 && settings.airbrushIntensity != 0)
            cachedNumPoints = 1;
    }

    /**
     * Returns the name associated with this tool.
     * 
     * @return the localized name of this tool
     */
    public String getDisplayName() {
        return PaintExample.getResourceString("tool.Airbrush.label");
    }

    /*
     * Template method for drawing
     */
    protected void render(Point point) {
        // Draws a bunch (cachedNumPoints) of random pixels within a specified circle (cachedRadiusSquared).
        ContainerFigure cfig = new ContainerFigure();

        for (int i = 0; i < cachedNumPoints; ++i) {
            int randX, randY;
            do {
                randX = (int) ((random.nextDouble() - 0.5) * settings.airbrushRadius * 2.0);
                randY = (int) ((random.nextDouble() - 0.5) * settings.airbrushRadius * 2.0);
            } while (randX * randX + randY * randY > cachedRadiusSquared);
            cfig.add(new PointFigure(settings.commonForegroundColor, point.x + randX, point.y + randY));
        }
        getPaintSurface().drawFigure(cfig);
    }
}