9 November 2020

Persistence in Draw.java

Here are our three classes that make up our draw program. What changes do we need to make so that we can serialize and deserialize state and restore the program.

Point.java This class gives locations on the canvas.


public class Point 
{
    private final double x;
    private final double y;
    public Point(double x, double y)
    {
        this.x = x;
        this.y = y;
    }
    public double getX()
    {
        return x;
    }
    public double getY()
    {
        return y;
    }
}

Curve.java This is a container for our drawing's curves. Curves know their color, their width, and how to draw themselves.


import java.util.ArrayList;
import javafx.scene.paint.Color;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;


public class Curve extends ArrayList<Point>
{
    private Color color;
    private double width;  //width of pen that draws the curve.
    public Curve(Color color, double width)
    {
        this.color = color;
        this.width = width;
    }
    public void draw(GraphicsContext pen)
    {
        pen.setStroke(color);
        pen.setLineWidth(width);
        pen.setLineCap(StrokeLineCap.ROUND);
        pen.setLineJoin(StrokeLineJoin.ROUND);
        pen.beginPath();  //starts the path
        pen.moveTo(get(0).getX(), get(0).getY());
        //there is nothing for the last point to join to.
        for(int k = 0;k < size() - 1 ;k++)
        {
            pen.lineTo(get(k + 1).getX(), get(k + 1).getY());
        }    
        pen.stroke();
    }

}

Draw.java This is the application.


/**************************************************
*   Author: Morrison
*   Date:  03 Nov 202020
**************************************************/

import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Slider;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import java.util.ArrayList;

public class Draw extends Application
{
    private final Canvas canvas;
    private final GraphicsContext pen;
    private Color bgColor;
    private Color currentColor;
    private ArrayList<Curve> drawing;
    private Curve curve;
    private double mainWidth;
    public Draw()
    {
        canvas = new Canvas(800, 600);
        pen = canvas.getGraphicsContext2D();
        bgColor = Color.WHITE;
        currentColor = Color.BLACK;
        drawing = new ArrayList<Curve>();
        mainWidth = 1;
        curve = null;
    }
    
    @Override
    public void init()
    {
    }

    @Override
    public void start(Stage primary)
    {
        primary.setTitle("Draw Application");
        BorderPane bp = new BorderPane();
        bp.setCenter(canvas);
        bp.setTop(buildMenus());
        canvas.setOnMousePressed( e -> 
        {
            curve = new Curve(currentColor, mainWidth);
            drawing.add(curve);
            curve.add(new Point(e.getX(), e.getY()));
        });
        canvas.setOnMouseDragged( e ->
        {
            curve.add(new Point(e.getX(), e.getY()));
            curve.draw(pen);
        });
        canvas.setOnMouseReleased( e -> 
        {
            curve.add(new Point(e.getX(), e.getY()));
            curve.draw(pen);
        });
        primary.setScene(new Scene(bp));
        primary.show();
    }

    private MenuBar buildMenus()
    {
        MenuBar mbar = new MenuBar();
        Menu fileMenu = new Menu("File");
        MenuItem quitItem = new MenuItem("Quit");
        fileMenu.setOnAction(e -> Platform.exit());
        fileMenu.getItems().addAll(quitItem);

        Menu colorMenu = new Menu("Color");
        //populate this color menu.
        colorMenu.getItems().addAll(
            new ColorMenuItem(Color.RED, "red"),
            new ColorMenuItem(Color.GREEN, "green"),
            new ColorMenuItem(Color.rgb(0, 0x1A, 0x57), "Dook"),
            new ColorMenuItem(Color.BLUE, "blue")
        );

        Menu bgMenu = new Menu("Background");
        //create and populate width menu
        Menu widthMenu = new Menu("Width");
        WidthMenuItem one = new WidthMenuItem(1);
        WidthMenuItem five = new WidthMenuItem(5);
        WidthMenuItem ten = new WidthMenuItem(10);
        WidthMenuItem twenty = new WidthMenuItem(20);
        WidthMenuItem fifty = new WidthMenuItem(50);
        MenuItem chooseItem = new MenuItem("Custom...");
        widthMenu.getItems().addAll(one, five, ten, twenty, fifty, chooseItem);
        chooseItem.setOnAction( e ->
        {
			Stage popup = new Stage();
			BorderPane p = new BorderPane();
			HBox hbox = new HBox();
			Button doneButton = new Button("Done");
			Slider size = new Slider(1, 200, mainWidth);
			Label number = new Label("Width of Pen: " + mainWidth);
			size.valueProperty().addListener((observable, oldValue, newValue) ->
			{
			  number.setText("Width of Pen: " + newValue.intValue());
			  mainWidth = newValue.intValue();
			});
			hbox.getChildren().addAll(number, size, doneButton);
			p.setTop(hbox);
			popup.setScene(new Scene(p, 300, 200));
			popup.show();
			doneButton.setOnAction(f -> popup.close());
        });

        mbar.getMenus().addAll(fileMenu, colorMenu, bgMenu,
            widthMenu);
        return mbar;
    }
    public void refresh()
    {   
        //draw the background
        pen.setFill(bgColor);
        pen.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
        ///redraw curves
        for( Curve c: drawing)
        {
            c.draw(pen);
        }
        //
    }
    @Override
    public void stop()
    {
    }
    class ColorMenuItem extends MenuItem
    {
        private final Color color;
        public ColorMenuItem(Color color, String name)
        {
            super(name);
            this.color = color;
            setOnAction( e -> 
            {
                currentColor = color;
            });
        }

    }
    class WidthMenuItem extends MenuItem
    {
        private final double width;
        public WidthMenuItem(double width)
        {
            super("" + width);
            this.width = width;
            setOnAction( e -> 
            {
                mainWidth = width;
            });
        }
    }
    class BgMenuItem extends MenuItem
    {
        private final Color color;
        public BgMenuItem(Color color, String name)
        {
            super(name);
            this.color = color;
            setOnAction( e -> 
            {
                bgColor = color;
                refresh();
            });
        }
    }

}

SerializableColor.java We have made color serializable. We now need to modify our programs so that state is serializable. What do we need to save so we can reconstitute a drawing? We should probably add a static factory method that converts any FX color to a serializable color and maybe vice versa, too.


import java.io.Serializable;
import javafx.scene.paint.Color;
public class SerializableColor implements Serializable
{
    private double red;
    private double green;
    private double blue;
    private double alpha;
    public SerializableColor(Color color)
    {
        this.red = color.getRed();
        this.green = color.getGreen();
        this.blue = color.getBlue();
        this.alpha = color.getOpacity();
    }
    public SerializableColor(double red, double green, double blue, double alpha)
    {
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.alpha = alpha;
    }
    public Color getFXColor()
    {
        return new Color(red, green, blue, alpha);
    }
	private String twoDigit(int n)
	{
		String out = Integer.toString(n, 16);
		return n < 16? "0" + out: out;
    }
    private int intColorLevel(double d)
    {
        return (int)(Math.round(255*d));
    }
	public String makeHexCode()
	{
        return String.format("#%s%s%s", twoDigit(intColorLevel(red)), twoDigit(intColorLevel(green)),
           twoDigit(intColorLevel(blue)));
	}
    public static void main(String[] args)
    {
        Color c = Color.ORANGE;
        SerializableColor sc = new SerializableColor(c);
        System.out.println(sc.makeHexCode());
   }
}