Jad Chahine
Jad Chahine

Reputation: 7149

How to delete lines in Java Swing?

I am looking to provide a additional option to my drawing application by giving the user the ability to delete lines that he has already draw on the JPanel.

The above code is about drawing lines on the JPanel

Can anyone provide me a method to delete a specific line?

Per example a method that provides the user the ability to click on a button in the window, then to select a specific line and delete.

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class DrawLine extends JPanel {

    private MouseHandler mouseHandler = new MouseHandler();
    private Point p1 = new Point(0, 0);
    private Point p2 = new Point(0, 0);
    private boolean drawing;

    //Store lines in an arraylist
    private ArrayList<Line> lines = new ArrayList<>();

    public DrawLine() {
        setBackground(Color.white);
        this.setPreferredSize(new Dimension(1200, 600));
        this.addMouseListener(mouseHandler);
        this.addMouseMotionListener(mouseHandler);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        //Grid start
        g.setColor(Color.lightGray);
        int sideLength = 20;
        int nRowCount = getHeight() / sideLength;
        int currentX = sideLength;
        for (int i = 0; i < nRowCount; i++) {
            g.drawLine(0, currentX, getWidth(), currentX);
            currentX = currentX + sideLength;
        }

        int nColumnCount = getWidth() / sideLength;
        int currentY = sideLength;
        for (int i = 0; i < nColumnCount; i++) {
            g.drawLine(currentY, 0, currentY, getHeight());
            currentY = currentY + sideLength;
        }
        //Grid end

        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.black);
        g2d.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setStroke(new BasicStroke(8,
            BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));
        g.drawLine(p1.x, p1.y, p2.x, p2.y);

        //draw all previous lines
        for (int i = 0; i < lines.size(); i++) { 
            g.drawLine(lines.get(i).p1.x, lines.get(i).p1.y, lines.get(i).p2.x, lines.get(i).p2.y);
        }
    }

    private class MouseHandler extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            drawing = true;
            p1 = e.getPoint();
            p2 = p1;
            repaint();
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            drawing = false;
            p2 = e.getPoint();
            repaint();
            lines.add(new Line(p1, p2));
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (drawing) {
                p2 = e.getPoint();
                repaint();
            }
        }
    }

    private void display() {
        JFrame f = new JFrame("LinePanel");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new DrawLine().display();
            }
        });
    }

    public class Line {
        Point p1;
        Point p2;

        public Line(Point p1, Point p2) {
            this.p1 = p1;
            this.p2 = p2;
        }
    }
} 

Delete a line means delete the line coordinates from the ArrayList, but my problem is how to handle a new mouse action when clicking a delete button and how to turn off the old mouse action (the one that let the user to draw lines)

Upvotes: 1

Views: 1413

Answers (3)

rdonuk
rdonuk

Reputation: 3956

At first for found the clicked line Line2D.Double(point1, point2).ptSegDist() method can be used. This method returning the distance between the line and the given point. If distance is smaller than 4 (because of your stroke width is 8) it means user clicked that line. That was the hardest part.

After that, I added a boolean member to Line class to keep the "clicked" information. And at last, in delete action I loop in lines list and remove the selected ones.

Note that, I am using isControlDown() for determine if user want to select a line or want to draw another line. Because somehow I had to decide what is the desired operation.

public class DrawLine extends JPanel {

    private static final int LINE_WIDTH = 8;

    private MouseHandler mouseHandler = new MouseHandler();
    private Point p1 = null;
    private Point p2 = null;
    private boolean drawing;

    private Point draggingPoint = null;

    // Store lines in an arraylist
    private ArrayList<Line> lines = new ArrayList<>();

    public DrawLine() {
        setBackground(Color.white);
        this.setPreferredSize(new Dimension(400, 200));
        this.addMouseListener(mouseHandler);
        this.addMouseMotionListener(mouseHandler);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        // Grid start
        g.setColor(Color.lightGray);
        int sideLength = 20;
        int nRowCount = getHeight() / sideLength;
        int currentX = sideLength;
        for (int i = 0; i < nRowCount; i++) {
            g.drawLine(0, currentX, getWidth(), currentX);
            currentX = currentX + sideLength;
        }

        int nColumnCount = getWidth() / sideLength;
        int currentY = sideLength;
        for (int i = 0; i < nColumnCount; i++) {
            g.drawLine(currentY, 0, currentY, getHeight());
            currentY = currentY + sideLength;
        }
        // Grid end

        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.red);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setStroke(new BasicStroke(LINE_WIDTH, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL));

        if (p1 != null && p2 != null) {
            g.drawLine(p1.x, p1.y, p2.x, p2.y);
        }

        // draw all previous lines
        for (int i = 0; i < lines.size(); i++) {
            Line line = lines.get(i);
            if (line.isSelected) {
                g2d.setColor(Color.red);
            } else {
                g2d.setColor(Color.blue);
            }
            g.drawLine(lines.get(i).p1.x, lines.get(i).p1.y, lines.get(i).p2.x, lines.get(i).p2.y);
        }
    }

    private void deSelectAll() {
        for (Line line : lines) {
            line.isSelected = false;
        }
    }

    private class MouseHandler extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            if (!e.isControlDown()) {
                drawing = true;
                p1 = e.getPoint();
                p2 = p1;
                repaint();
            } else {
                for (Line line : lines) {
                    line.isSelected = false;
                    if (isInside(line, e.getPoint())) {
                        line.isSelected = true;
                    }
                }

                draggingPoint = e.getPoint();

                repaint();
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (!e.isControlDown()) {
                drawing = false;
                p2 = e.getPoint();
                repaint();
                deSelectAll();
                lines.add(new Line(p1, p2));

                p1 = null;
                p2 = null;
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (!e.isControlDown()) {
                if (drawing) {
                    p2 = e.getPoint();
                    repaint();
                }
            } else if (draggingPoint != null) {

                int XDiff = e.getPoint().x - draggingPoint.x;
                int YDiff = e.getPoint().y - draggingPoint.y;

                draggingPoint = e.getPoint();

                for (Line line : lines) {
                    if (line.isSelected) {
                        line.p1.x = line.p1.x + XDiff;
                        line.p1.y = line.p1.y + YDiff;
                        line.p2.x = line.p2.x + XDiff;
                        line.p2.y = line.p2.y + YDiff;
                    }
                }

                repaint();
            }
        }

        /**
         * Returns true if the given point is inside the given line.
         */
        private boolean isInside(Line line, Point p) {
            return new Line2D.Double(line.p1, line.p2).ptSegDist(p) < (LINE_WIDTH / 2);
        }
    }

    private void display() {
        JFrame f = new JFrame("LinePanel");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);

        f.add(new JButton(new DeleteLineAction()), BorderLayout.SOUTH);

        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private class DeleteLineAction extends AbstractAction {

        public DeleteLineAction() {
            super("Delete");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Iterator<Line> iterator = lines.iterator();

            while (iterator.hasNext()) {
                Line line = iterator.next();

                if (line.isSelected) {
                    iterator.remove();
                }
            }

            repaint();
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new DrawLine().display();
            }
        });
    }

    class Line {
        Point p1;
        Point p2;

        boolean isSelected;

        public Line(Point p1, Point p2) {
            this.p1 = p1;
            this.p2 = p2;

            isSelected = true;
        }
    }
}

Upvotes: 3

Slubberdegullion
Slubberdegullion

Reputation: 159

What you would most likely have to do is have some kind of line manager that stores the lines that you want drawn in a list. Any time you add or remove a line, the line manager would tell the frame to redraw itself.

In order to check if a user has clicked on the line for the selection is to implement Circle to Line collision, using the mouse position for the circle, and a radius of your choice (1px would be fine if you wanted it exactly where the mouse was clicked). This link shows the math behind such a collision: http://devmag.org.za/2009/04/17/basic-collision-detection-in-2d-part-2/

An easier solution if you aren't friends with math is to iterate through the lines of the line manager, highlighting the one that the user is currently on in a different colour, rather than having them click to select the line.

Upvotes: 1

Pixelstix
Pixelstix

Reputation: 772

You will want some other gesture to indicate line removal. Perhaps add a check for a RMB (Right mouse button) click in your MouseHandler, which will then try to find a Line that the click corresponds to. Then remove the Line from the ArrayList, and call repaint(). When paint is called, there will be one fewer Lines to paint.

Upvotes: 0

Related Questions