How to draw curved lines on the screen with LinkedList and MouseListeners?

76 Views Asked by At

I am new to Java, as for my class's final project, I am developing a paint application that enables users to draw rich curved lines and common geometric shapes like rectangle and oval, also users can undo their shape drawing.

My app worked well on shape drawing. When I want to draw shapes, the shapes I want to draw are stored in the LinkedList which will be called to draw shapes on the screen When I applied this LinkedList concept to draw curved lines on the screen. I modified shape drawing code to draw curved lines. I ran to a problem; The result I got was little dots on the screen. If I tried to copy similar code that draws shapes for drawing curved lines, I would get straight lines instead of the curved lines.

I think this problem is related to MouseListener interfaces. I need some suggestions on how can I modify my MouseListener methods so that I can draw the curved lines on the screen properly.

Changes to my source code are welcome:

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class PaintAppPlusSecondDraft extends JApplet implements ActionListener {
private static final long serialVersionUID = 1L;

private JMenuBar menuBar = new JMenuBar();

private JMenu mainMenu = new JMenu("Main menu");
private JMenuItem howToUse = new JMenuItem("How to use?");
private JMenuItem toDefaultMode = new JMenuItem("Return to default mode");

private JMenu boardSettings = new JMenu("Paint board settings");
private JMenuItem clearBoard = new JMenuItem("Clear screen");
private JMenuItem toDefaultBoard = new JMenuItem("Set to default paint board");
private JMenuItem bCustom = new JMenuItem("Set background colour");

private JMenu brushSettings = new JMenu("Paintbrush settings");
private JMenuItem eraser = new JMenuItem("Eraser");
private JMenuItem toDefaultBrush = new JMenuItem("Set to default paintbrush");
private JMenu setBrushSize = new JMenu("Set paintbrush size");
private JMenuItem Two = new JMenuItem("2 pixels");
private JMenuItem Four = new JMenuItem("4 pixels");
private JMenuItem Six = new JMenuItem("6 pixels");
private JMenuItem Eight = new JMenuItem("8 pixels");
private JMenuItem Ten = new JMenuItem("10 pixels");
private JMenu setBrushType = new JMenu("Set paintbrush type");
private JMenuItem defaultType1 = new JMenuItem("Default");
private JMenuItem defaultType2 = new JMenuItem("Default (light stroke)");
private JMenuItem Custom = new JMenuItem("Set paintbrush colour");

private JMenu drawShapes = new JMenu("Draw shapes");
private JMenuItem undoShape = new JMenuItem("Undo shape drawing");
private JMenuItem StraightLine = new JMenuItem("Straight line");
private JMenuItem Rectangle = new JMenuItem("Rectangle");
private JMenuItem Oval = new JMenuItem("Oval");
private JMenuItem filledRectangle = new JMenuItem("Filled rectangle");
private JMenuItem filledOval = new JMenuItem("Filled oval");

private int prevBrushSize = PaintBoard.brushSize;
private int prevBrushType = PaintBoard.brushType;
private Color prevBrushColour = PaintBoard.currentColour; /* For better user experience */

public void init() {
    Frame frame = (Frame) getParent().getParent();
    frame.setTitle("JAVA Paint plus");
    frame.setResizable(false);

    this.setSize(600, 400);
    this.setContentPane(new PaintBoard());

    mainMenu.add(howToUse);
    mainMenu.add(toDefaultMode);
    mainMenu.add(about);

    boardSettings.add(clearBoard);
    boardSettings.add(toDefaultBoard);
    boardSettings.add(bCustom);

    brushSettings.add(eraser);
    brushSettings.add(toDefaultBrush);
    setBrushSize.add(Two);
    setBrushSize.add(Four);
    setBrushSize.add(Six);
    setBrushSize.add(Eight);
    setBrushSize.add(Ten);
    brushSettings.add(setBrushSize);
    setBrushType.add(defaultType1);
    setBrushType.add(defaultType2);
    setBrushType.add(waterColourBrush);
    setBrushType.add(triangleType);
    setBrushType.add(squareType);
    setBrushType.add(hexagonType);
    setBrushType.add(starType);
    setBrushType.add(heartType);
    brushSettings.add(setBrushType);
    brushSettings.add(Custom);

    drawShapes.add(undoShape);
    drawShapes.add(StraightLine);
    drawShapes.add(Rectangle);
    drawShapes.add(Oval);
    drawShapes.add(filledRectangle);
    drawShapes.add(filledOval);

    menuBar.add(mainMenu);
    menuBar.add(boardSettings);
    menuBar.add(brushSettings);
    menuBar.add(drawShapes);

    howToUse.addActionListener(this);
    toDefaultMode.addActionListener(this);

    clearBoard.addActionListener(this);
    toDefaultBoard.addActionListener(this);
    bCustom.addActionListener(this);

    eraser.addActionListener(this);
    toDefaultBrush.addActionListener(this);
    Two.addActionListener(this);
    Four.addActionListener(this);
    Six.addActionListener(this);
    Eight.addActionListener(this);
    Ten.addActionListener(this);
    defaultType1.addActionListener(this);
    defaultType2.addActionListener(this);
    Custom.addActionListener(this);

    undoShape.addActionListener(this);
    StraightLine.addActionListener(this);
    Rectangle.addActionListener(this);
    Oval.addActionListener(this);
    filledRectangle.addActionListener(this);
    filledOval.addActionListener(this);

    this.setJMenuBar(menuBar);
    this.setVisible(true);
}

@Override
public void actionPerformed(ActionEvent e) {
    if (e.getSource() == howToUse) {
        JOptionPane.showMessageDialog(null, "Press your mouse on the board and drag to draw!", "How to use?", JOptionPane.INFORMATION_MESSAGE);
    }

    if (e.getSource() == toDefaultMode) {
        PaintBoard.canvasColour = Color.WHITE;
        PaintBoard.brushSize = prevBrushSize = 6;
        PaintBoard.brushType = prevBrushType = 1;
        PaintBoard.currentColour = prevBrushColour = Color.BLACK;
        PaintBoard.shapes.makeEmpty();
        repaint();
    }

    if (e.getSource() == clearBoard) {
        PaintBoard.shapes.makeEmpty();
        repaint();
    }

    if (e.getSource() == toDefaultBoard) {
        PaintBoard.canvasColour = Color.WHITE;
        repaint();
    }

    if (e.getSource() == bCustom) {
        try {
            Color customColour = JColorChooser.showDialog(null, "Select colour:", PaintBoard.canvasColour);

            PaintBoard.canvasColour = customColour;
            PaintBoard.shapes.makeEmpty();
            repaint();
        } catch (NullPointerException ex) {

        }
    }

    if (e.getSource() == eraser) {
        prevBrushSize = PaintBoard.brushSize;
        prevBrushType = PaintBoard.brushType;
        prevBrushColour = PaintBoard.currentColour;
        PaintBoard.brushSize = 44;
        PaintBoard.brushType = 1;
        PaintBoard.currentColour = PaintBoard.canvasColour;
    }

    if (e.getSource() == toDefaultBrush) {
        PaintBoard.brushSize = prevBrushSize = 6;
        PaintBoard.brushType = prevBrushType = 1;
        PaintBoard.currentColour = prevBrushColour = Color.BLACK;
    }

    if (e.getSource() == Two) {
        PaintBoard.brushSize = prevBrushSize = 2;
        PaintBoard.brushType = prevBrushType;
        PaintBoard.currentColour = prevBrushColour;
    }

    if (e.getSource() == Four) {
        PaintBoard.brushSize = prevBrushSize = 4;
        PaintBoard.brushType = prevBrushType;
        PaintBoard.currentColour = prevBrushColour;
    }

    if (e.getSource() == Six) {
        PaintBoard.brushSize = prevBrushSize = 6;
        PaintBoard.brushType = prevBrushType;
        PaintBoard.currentColour = prevBrushColour;
    }

    if (e.getSource() == Eight) {
        PaintBoard.brushSize = prevBrushSize = 8;
        PaintBoard.brushType = prevBrushType;
        PaintBoard.currentColour = prevBrushColour;
    }

    if (e.getSource() == Ten) {
        PaintBoard.brushSize = prevBrushSize = 10;
        PaintBoard.brushType = prevBrushType;
        PaintBoard.currentColour = prevBrushColour;
    }

    if (e.getSource() == defaultType1) {
        PaintBoard.brushSize = prevBrushSize;
        PaintBoard.brushType = prevBrushType = 1;
        PaintBoard.currentColour = prevBrushColour;
    }

    if (e.getSource() == defaultType2) {
        PaintBoard.brushSize = prevBrushSize;
        PaintBoard.brushType = prevBrushType = 2;
        PaintBoard.currentColour = prevBrushColour;
    }

    if (e.getSource() == Custom) {
        try {
            Color customColour = JColorChooser.showDialog(null, "Select colour:", PaintBoard.currentColour);

            PaintBoard.brushSize = prevBrushSize;
            PaintBoard.brushType = prevBrushType;
            PaintBoard.currentColour = prevBrushColour = customColour;
        } catch (NullPointerException ex) {

        }
    }

    if (e.getSource() == undoShape) {
        try {
            PaintBoard.shapes.removeFront();
            repaint();
        } catch (NullPointerException ex) {

        }
    }

    if (e.getSource() == StraightLine) {
        PaintBoard.brushSize = prevBrushSize;
        PaintBoard.currentColour = prevBrushColour;
        PaintBoard.shapeType = 1;
    }

    if (e.getSource() == Rectangle) {
        PaintBoard.currentColour = prevBrushColour;
        PaintBoard.shapeType = 2;
        PaintBoard.filled = false;
    }

    if (e.getSource() == Oval) {
        PaintBoard.currentColour = prevBrushColour;
        PaintBoard.shapeType = 3;
        PaintBoard.filled = false;
    }

    if (e.getSource() == filledRectangle) {
        PaintBoard.currentColour = prevBrushColour;
        PaintBoard.shapeType = 2;
        PaintBoard.filled = true;
    }

    if (e.getSource() == filledOval) {
        PaintBoard.currentColour = prevBrushColour;
        PaintBoard.shapeType = 3;
        PaintBoard.filled = true;
    }
}

abstract static class brushLine {
    private int brushSize;
    private int brushType;
    private Color brushColour;

    public brushLine() {
        brushSize = 6;
        brushType = 1;
        brushColour = Color.BLACK;
    }

    public brushLine(int brushSize, int brushType, Color brushColour) {
        this.brushSize = brushSize;
        this.brushType = brushType;
        this.brushColour = brushColour;
    }

    public void setBrushSize(int brushSize) {
        this.brushSize = brushSize;
    }

    public void setBrushType(int brushType) {
        this.brushType = brushType;
    }

    public void setBrushColour(Color brushColour) {
        this.brushColour = brushColour;
    }

    public int getBrushSize() {
        return brushSize;
    }

    public int getBrushType() {
        return brushType;
    }

    public Color getBrushColour() {
        return brushColour;
    }

    abstract void show(Graphics window);
}

static class paintBrushLine extends brushLine {
    private int x1, y1, x2, y2;

    public paintBrushLine() {
        super();
        x1 = 0;
        y1 = 0;
        x2 = 0;
        y2 = 0;
    }

    public paintBrushLine(int brushSize, int brushType, Color brushColour, int x1, int y1, int x2, int y2) {
        super(brushSize, brushType, brushColour);
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    public void setX1(int x1) {
        this.x1 = x1;
    }

    public void setY1(int y1) {
        this.y1 = y1;
    }

    public void setX2(int x2) {
        this.x2 = x2;
    }

    public void setY2(int y2) {
        this.y2 = y2;
    }

    public int getX1() {
        return x1;
    }

    public int getY1() {
        return y1;
    }

    public int getX2() {
        return x2;
    }

    public int getY2() {
        return y2;
    }

    public void show(Graphics window) {
        window.setColor(getBrushColour());

        switch (getBrushType()) {

        case 1://Default brush
            ((Graphics2D) window).setStroke(new BasicStroke(getBrushSize(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            window.drawLine(getX1(), getY1(), getX2(), getY2());
            break;
        case 2://Default brush (light stroke) --> Makes empty squares with few randomly positioned pixels
            int[] pixelPos = new int[2];

            for (int i = 0; i < ((getBrushSize()*getBrushSize()) / 10); i++) {
                pixelPos[0] = new Random().nextInt(getBrushSize());
                pixelPos[1] = new Random().nextInt(getBrushSize());

                window.drawRect(getX1() + pixelPos[0], getY1() + pixelPos[1], 1, 1);
            }

            break;
        }
    }
}

abstract static class Shape {
    private int x1, y1, x2, y2;
    private int thickness;
    private Color shapeColour;

    public Shape() {
        x1 = 0;
        y1 = 0;
        x2 = 0;
        y2 = 0;
        shapeColour = Color.BLACK;
    }

    public Shape(int x1, int y1, int x2, int y2, int thickness, Color shapeColour)
    {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        this.thickness = thickness;
        this.shapeColour = shapeColour;
    }

    public void setX1(int x1) {
        this.x1 = x1;
    }

    public void setY1(int y1) {
        this.y1 = y1;
    }

    public void setX2(int x2) {
        this.x2 = x2;
    }

    public void setY2(int y2) {
        this.y2 = y2;
    }

    public void setThickness(int thickness) {
        this.thickness = thickness;
    }

    public void setShapeColour(Color shapeColour) {
        this.shapeColour = shapeColour;
    }

    public int getX1() {
        return x1;
    }

    public int getY1() {
        return y1;
    }

    public int getX2() {
        return x2;
    }

    public int getY2() {
        return y2;
    }

    public int getThickness() {
        return thickness;
    }

    public Color getShapeColour() {
        return shapeColour;
    }

    abstract void sketch(Graphics window);
}

abstract static class boundedShape extends Shape {
    private int shapeType;
    private boolean filled;

    public boundedShape() {
        super();
        filled = false;
    }

    public boundedShape(int x1, int y1, int x2, int y2, int thickness, Color shapeColour, int shapeType, boolean filled) {
        super(x1, y1, x2, y2, thickness, shapeColour);
        this.shapeType = shapeType;
        this.filled = filled;
    }

    public void setShapeType(int shapeType) {
        this.shapeType = shapeType;
    }

    public void setFilled(boolean filled) {
        this.filled = filled;
    }

    public int getUpperLeftX() {
        return Math.min(getX1(), getX2());
    }

    public int getUpperLeftY() {
        return Math.min(getY1(), getY2());
    }

    public int getWidth() {
        return Math.abs(getX1() - getX2());
    }


    public int getHeight() {
        return Math.abs(getY1() - getY2());
    }

    public int getShapeType() {
        return shapeType;
    }

    public boolean getFilled() {
        return filled;
    }

    abstract public void sketch(Graphics window);
}

static class geometricShape extends boundedShape {
    public geometricShape() {
        super();
    }

    public geometricShape(int x1, int y1, int x2, int y2, int thickness, Color shapeColour, int shapeType, boolean filled) {
        super(x1, y1, x2, y2, thickness, shapeColour, shapeType, filled);
    }

    public void sketch(Graphics window) {
        window.setColor(getShapeColour());

        switch(getShapeType()) {

        case 1://Straight line
            ((Graphics2D) window).setStroke(new BasicStroke(getThickness(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            window.drawLine(getX1(), getY1(), getX2(), getY2());
            break;
        case 2://Rectangle
            if (getFilled())
                window.fillRect(getUpperLeftX(), getUpperLeftY(), getWidth(), getHeight());
            else {
                ((Graphics2D) window).setStroke(new BasicStroke(getThickness(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
                window.drawRect(getUpperLeftX(), getUpperLeftY(), getWidth(), getHeight());
            }

            break;
        case 3://Oval
            if (getFilled())
                window.fillOval(getUpperLeftX(), getUpperLeftY(), getWidth(), getHeight());
            else {
                ((Graphics2D) window).setStroke(new BasicStroke(getThickness(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
                window.drawOval(getUpperLeftX(), getUpperLeftY(), getWidth(), getHeight());
            }

            break;
        }
    }
}

static class PaintBoard extends JPanel implements MouseMotionListener, MouseListener {
    private static final long serialVersionUID = 1L;

    private boolean painting;
    private int prevX, prevY;
    private static Color canvasColour = Color.WHITE;
    private static int brushSize = 6;
    private static int brushType = 1;
    private static Color currentColour = Color.BLACK;

    private static LinkedList<brushLine> brushLines = new LinkedList<>();
    private brushLine currentBrushLine;

    private static LinkedList<Shape> shapes = new LinkedList<>();
    private static int shapeType = 0;
    private Shape currentShape = null;
    private static boolean filled;

    private JLabel mouseCoordinates = new JLabel("X: 0 pixels     Y: 0 pixels");

    public PaintBoard() {
        setSize(getWidth(), getHeight());
        setLayout(new BorderLayout());
        add(mouseCoordinates, BorderLayout.SOUTH);
        addMouseMotionListener(this);
        addMouseListener(this);
    }

    @Override
    public void paintComponent(Graphics board) {
        super.paintComponent(board);

        board.setColor(canvasColour);
        board.fillRect(0, 0, getWidth(), getHeight());

        ArrayList<brushLine> linesToDraw = brushLines.getArray();

        for (int i = linesToDraw.size() - 1; i >= 0; i--)
            linesToDraw.get(i).show(board);

        if (currentBrushLine != null)
            currentBrushLine.show(board);

        ArrayList<Shape> shapesToDraw = shapes.getArray();

        for (int j = shapesToDraw.size() - 1; j >= 0; j--)
            shapesToDraw.get(j).sketch(board);

        if (currentShape != null)
            currentShape.sketch(board);
    }

    public void mouseDragged(MouseEvent e) {
        mouseCoordinates.setText(String.format("X: %d pixels     Y: %d pixels", e.getX(), e.getY()));

        if (shapeType > 0) {
            currentShape.setX2(e.getX());
            currentShape.setY2(e.getY());
            repaint();
        } else {
            if (!painting)
                return;

            ((paintBrushLine) currentBrushLine).setX1(prevX);
            ((paintBrushLine) currentBrushLine).setY1(prevY);
            ((paintBrushLine) currentBrushLine).setX2(e.getX());
            ((paintBrushLine) currentBrushLine).setY2(e.getY());
            repaint();

            prevX = e.getX();
            prevY = e.getY();
        }
    }

    public void mouseMoved(MouseEvent e) {
        mouseCoordinates.setText(String.format("X: %d pixels     Y: %d pixels", e.getX(), e.getY()));
    }

    public void mouseClicked(MouseEvent e) {

    }

    public void mouseEntered(MouseEvent e) {

    }

    public void mouseExited(MouseEvent e) {

    }

    public void mousePressed(MouseEvent e) {
        switch (shapeType) {

        case 0://Curved line
            if (painting)
                return;

            prevX = e.getX();
            prevY = e.getY();

            currentBrushLine = new paintBrushLine(brushSize, brushType, currentColour, prevX, prevY, prevX, prevY);

            painting = true;
            break;
        default://Other shapes
            currentShape = new geometricShape(e.getX(), e.getY(), e.getX(), e.getY(), brushSize, currentColour, shapeType, filled);
            painting = false;
            break;
        }
    }

    public void mouseReleased(MouseEvent e) {
        if (shapeType > 0) {
            currentShape.setX2(e.getX());
            currentShape.setY2(e.getY());

            shapes.addFront(currentShape);

            currentShape = null;
            shapeType = 0;
            repaint();
        } else {
            if (!painting)
                return;

            ((paintBrushLine) currentBrushLine).setX2(e.getX());
            ((paintBrushLine) currentBrushLine).setY2(e.getY());

            brushLines.addFront(currentBrushLine);
            currentBrushLine = null;
            painting = false;
            repaint();
        }
    }
}
}

LinkedList class:

import java.util.ArrayList;

class LinkedList<T> {
private int numberOfNodes = 0; 
private ListNode<T> front = null;

// Returns true if the linked list has no nodes, or false otherwise.
public boolean isEmpty() {
    return (front == null);
}

// Deletes all of the nodes in the linked list.
// Note: ListNode objects will be automatically garbage collected by JVM.
public void makeEmpty() {
    front = null;
    numberOfNodes = 0;
}

// Returns the number of nodes in the linked list
public int size() {
    return numberOfNodes;
}

// Adds a node to the front of the linked list.
public void addFront( T element ) {
    front = new ListNode<T>( element, front );
    numberOfNodes++;
}

// Returns a reference to the data in the first node, or null if the list is empty.
public T peek() {
    if (isEmpty()) 
        return null;

    return front.getData();
}

// Removes a node from the front of the linked list (if there is one).
// Returns a reference to the data in the first node, or null if the list is empty.
@SuppressWarnings("unchecked")
public T removeFront() {
    T tempData;

    if (isEmpty()) 
        return null;

    tempData = front.getData();
    front = front.getNext();
    numberOfNodes--;
    return tempData;
}

@SuppressWarnings("unchecked")
public void removeEnd(T element) {
    ListNode<T> node=front;
    while(node.getNext() != null)
    {
        node = node.getNext();
    }
    node.setNext(new ListNode<T>((T)element, null));
}

// Return array filled with T objects
@SuppressWarnings("unchecked")
public ArrayList<T> getArray() {

    ArrayList<T> shapeArray=new ArrayList<T>();

    ListNode<T> node=front;
    while (node!=null)
    {
        shapeArray.add(node.getData());
        node = node.getNext();
    }

    return shapeArray;
}
}

ListNode class:

public class ListNode<T> {
private T data;
private ListNode next;

// Constructor: No reference to next node provided so make it null 
public ListNode( T nodeData ) {
    this( nodeData, null);
}

// Constructor: Set data and reference to next node.
public ListNode( T nodeData, ListNode nodeNext ) {
    data = nodeData;
    next = nodeNext;
}

// Accessor: Return the data for current ListNode object
public T getData() {
    return data;
}

// Accessor: Return reference to next ListNode object
public ListNode getNext() {
    return next;
}

// Mutator: Set new data for current ListNode object
public void setData( T newData ) {
    data = newData;
}


// Mutator: Set new reference to the next node object
public void setNext( ListNode newNext ) {
    next = newNext;
}
}
1

There are 1 best solutions below

0
sorifiend On

Both your shapes and your lines only store a start and end point.

abstract static class Shape {
    private int x1, y1, x2, y2;
    private int thickness;
    private Color shapeColour;

So how does your paint method know what to do? How can it draw a curve when it only ever knows two points?

What you need to do:

Once you have the shape completed you need to do some math to work out the radius and coordinates of each arc based on the coordinates of the previous and next segment, and then in your paint method you can draw lots and lots of small lines so that it appears a nice smooth arc.

Also, add some debugging code to find out what values your paint method is using, you may find that the reason you only see dots or small lines is because the values for each shape/line is not being stored correctly.