0

I am working on an GUI App in Java that allows me to draw different shapes with different colors. I have two different radio buttons groups for shapes and for colors. By default each group has the random button selected. And it works. But I want to select something else and draw the selected shape with the selected color. The thing is that I don't know how to update when I select a different radio button. Each selection works if I put it as default. But once I start the program it wil only draw the default selected choice. This is what I have so far:

Main.java

package com.daema.P5;
import java.awt.*;

import javax.swing.*;

public class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                //////CREATING THE FRAME WITH TITLE AND SET THE FRAME LAYOUT TO GRID///////////////
                JFrame f = new JFrame("Shapes on click Application");
                GridBagLayout frameGridBagLayout = new GridBagLayout();
                GridBagConstraints constraints = new GridBagConstraints();
                f.setFont(new Font("SansSerif", Font.PLAIN, 14));
                f.setLayout(frameGridBagLayout);

    /////CREATING TOP PANEL WHICH CONTAINS THE OPTIONS/////////
                /////CREATING COLORS PANEL//////////
                JPanel topPanelColors = new JPanel();
                JLabel colorsLabel = new JLabel("Culoare: ");
                ButtonGroup colorsGroup = new ButtonGroup();
                JRadioButton rosu = new JRadioButton();
                rosu.setText("Rosu");
                rosu.setActionCommand("rosu");
                JRadioButton verde = new JRadioButton();
                verde.setText("Verde");
                verde.setActionCommand("verde");
                JRadioButton albastru = new JRadioButton();
                albastru.setText("Albastru");
                albastru.setActionCommand("albastru");
                JRadioButton randomColor = new JRadioButton();
                randomColor.setText("Random");
                randomColor.setActionCommand("randomColor");
                randomColor.setSelected(true);
                colorsGroup.add(rosu);
                colorsGroup.add(verde);
                colorsGroup.add(albastru);
                colorsGroup.add(randomColor);

                topPanelColors.add(colorsLabel);
                topPanelColors.add(rosu);
                topPanelColors.add(verde);
                topPanelColors.add(albastru);
                topPanelColors.add(randomColor);

                constraints.gridy = GridBagConstraints.PAGE_START;
                f.add(topPanelColors, constraints);

                /////CREATING SHAPES PANEL/////////
                JPanel topPanelShapes = new JPanel();
                JLabel shapesLabel = new JLabel("Forma: ");
                ButtonGroup shapesGroup = new ButtonGroup();
                JRadioButton cerc = new JRadioButton();
                cerc.setText("Cerc");
                cerc.setActionCommand("cerc");
                JRadioButton patrat = new JRadioButton();
                patrat.setText("Patrat");
                patrat.setActionCommand("patrat");
                JRadioButton triunghi = new JRadioButton();
                triunghi.setText("Triungi");
                triunghi.setActionCommand("triunghi");
                JRadioButton randomShape = new JRadioButton();
                randomShape.setText("Random");
                randomShape.setActionCommand("randomShape");
                randomShape.setSelected(true);
                shapesGroup.add(cerc);
                shapesGroup.add(patrat);
                shapesGroup.add(triunghi);
                shapesGroup.add(randomShape);

                topPanelShapes.add(shapesLabel);
                topPanelShapes.add(cerc);
                topPanelShapes.add(patrat);
                topPanelShapes.add(triunghi);
                topPanelShapes.add(randomShape);

                constraints.gridy = GridBagConstraints.LINE_START;
                f.add(topPanelShapes, constraints);


                ///////CREATING THE BOTTOM PANEL WHICH CONTAINS THE DRAWING PANEL////////////
                JPanel bottomPanel = new JPanel();

                DrawPanel p = new DrawPanel(10f, shapesGroup.getSelection().getActionCommand(), colorsGroup.getSelection().getActionCommand());
                p.setPreferredSize(new Dimension(420, 420));

                bottomPanel.add(p);

                constraints.gridy = GridBagConstraints.LAST_LINE_START;
                f.add(bottomPanel, constraints);

                f.pack();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }
}

DrawPanel.java

package com.daema.P5;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.*;

public class DrawPanel extends JPanel implements MouseListener {
    @Serial
    private static final long serialVersionUID = -6817035652787391530L;

    private final List<Shape> shapes;
    protected float radius;
    protected String shape;
    protected String color;

    public DrawPanel(float radius, String shape, String color) {
        this.shapes = new ArrayList<>();
        this.radius = radius;
        this.shape = shape;
        this.color = color;

        setBackground(Color.WHITE);
        addMouseListener(this);
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        for (Shape shape : shapes) {
            shape.paintComponent(g);
        }
    }

    public int getRandomNumber() {
        Random random = new Random();
        return random.nextInt(4 - 1) + 1;
    }

    @Override
    public void mouseClicked(MouseEvent e) {

        float sat = 0.7f;
        float bri = 0.8f;
        switch (shape) {
            case "cerc" -> {
                switch (color) {
                    case "rosu" -> shapes.add(new Circle(e.getX(), e.getY(), radius, Color.RED));
                    case "verde" -> shapes.add(new Circle(e.getX(), e.getY(), radius, Color.GREEN));
                    case "albastru" -> shapes.add(new Circle(e.getX(), e.getY(), radius, Color.BLUE));
                    case "randomColor" -> shapes.add(new Circle(e.getX(), e.getY(), radius, ColorUtils.randHue(sat, bri)));
                }
            }
            case "patrat" -> {
                switch (color) {
                    case "rosu" -> shapes.add(new Square(e.getX(), e.getY(), radius * 2, Color.RED));
                    case "verde" -> shapes.add(new Square(e.getX(), e.getY(), radius * 2, Color.GREEN));
                    case "albastru" -> shapes.add(new Square(e.getX(), e.getY(), radius * 2, Color.BLUE));
                    case "randomColor" -> shapes.add(new Square(e.getX(), e.getY(), radius * 2, ColorUtils.randHue(sat, bri)));
                }
            }
            case "triunghi" -> {
                switch (color) {
                    case "rosu" -> shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, Color.RED));
                    case "verde" -> shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, Color.GREEN));
                    case "albastru" -> shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, Color.BLUE));
                    case "randomColor" -> shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, ColorUtils.randHue(sat, bri)));
                }
            }
            case "randomShape" -> {
                int random = getRandomNumber();
                switch (color) {
                    case "rosu" -> {
                        switch (random) {
                            case 1 -> shapes.add(new Circle(e.getX(), e.getY(), radius, Color.RED));
                            case 2 -> shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, Color.RED));
                            case 3 -> shapes.add(new Square(e.getX(), e.getY(), radius * 2, Color.RED));
                            default -> throw new IllegalStateException("Unexpected value: " + random);
                        }
                    }
                    case "verde" -> {
                        switch (random) {
                            case 1 -> shapes.add(new Circle(e.getX(), e.getY(), radius, Color.GREEN));
                            case 2 -> shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, Color.GREEN));
                            case 3 -> shapes.add(new Square(e.getX(), e.getY(), radius * 2, Color.GREEN));
                            default -> throw new IllegalStateException("Unexpected value: " + random);
                        }
                    }
                    case "albastru" -> {
                        switch (random) {
                            case 1 -> shapes.add(new Circle(e.getX(), e.getY(), radius, Color.BLUE));
                            case 2 -> shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, Color.BLUE));
                            case 3 -> shapes.add(new Square(e.getX(), e.getY(), radius * 2, Color.BLUE));
                            default -> throw new IllegalStateException("Unexpected value: " + random);
                        }
                    }
                    case "randomColor" -> {
                        switch (random) {
                            case 1 -> shapes.add(new Circle(e.getX(), e.getY(), radius, ColorUtils.randHue(sat, bri)));
                            case 2 -> shapes.add(new Triangle(e.getX(), e.getY(), radius * 2, ColorUtils.randHue(sat, bri)));
                            case 3 -> shapes.add(new Square(e.getX(), e.getY(), radius * 2, ColorUtils.randHue(sat, bri)));
                            default -> throw new IllegalStateException("Unexpected value: " + random);
                        }
                    }
                }
            }
        }
        repaint();
    }

    @Override
    public void mousePressed(MouseEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseExited(MouseEvent e) {
        // TODO Auto-generated method stub
    }
}

Shape.java

package com.daema.P5;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;

public interface Shape {
    Point getOrigin();
    void setOrigin(Point origin);

    Color getColor();
    void setColor(Color color);

    void paintComponent(Graphics g);
}

Circle.java

package com.daema.P5;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;

public class Circle implements Shape {
    private Point origin;
    private float radius;
    private Color color;

    public Circle() {
        this(0, 0, 0.5f, Color.BLACK);
    }

    public Circle(int x, int y, float radius, Color color) {
        this(new Point(x, y), radius, color);
    }

    public Circle(Point origin, float radius, Color color) {
        this.origin = origin;
        this.radius = radius;
        this.color = color;
    }

    @Override
    public Point getOrigin() {
        return origin;
    }

    @Override
    public void setOrigin(Point origin) {
        this.origin = origin;
    }

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
    }

    @Override
    public Color getColor() {
        return color;
    }

    @Override
    public void setColor(Color color) {
        this.color = color;
    }

    @Override
    public void paintComponent(Graphics g) {
        int diameter = (int) (this.radius * 2);
        int x = (int) (origin.x - this.radius);
        int y = (int) (origin.y - this.radius);

        g.setColor(this.color);
        g.fillOval(x, y, diameter, diameter);
    }
}

Triangle.java

package com.daema.P5;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.geom.Point2D;

public class Triangle implements Shape {
    private Point origin;
    private float side;
    private Color color;

    private Polygon poly;

    public Triangle(int x, int y, float side, Color color) {
        this(new Point(x, y), side, color);
    }

    public Triangle(Point origin, float side, Color color) {
        this.origin = origin;
        this.side = side;
        this.color = color;

        recalculate();
    }

    protected void recalculate() {
        this.poly = createPolygon((float) origin.getX(), (float) origin.getY(), side, false);
    }

    protected Polygon createPolygon(float x, float y, float side, boolean invert) {
        float xOff = side / 2f;
        float yOff = (float) (xOff * Math.sqrt(3));

        float r1 = 1f / 3f;
        float r2 = 2f / 3f;

        if (invert) {
            yOff *= -1;
        }

        return createPolygon(new Point2D.Float[] {
                new Point2D.Float(x,        y - (yOff * r2)), // Top
                new Point2D.Float(x - xOff, y + (yOff * r1)), // Left
                new Point2D.Float(x + xOff, y + (yOff * r1))  // Right
        });
    }

    protected Polygon createPolygon(Point2D.Float[] points) {
        int nPoints = points.length + 1;
        int[] xCoords = new int[nPoints];
        int[] yCoords = new int[nPoints];

        for (int i = 0; i < nPoints; i++) {
            xCoords[i] = (int) points[i % points.length].x;
            yCoords[i] = (int) points[i % points.length].y;
        }

        return new Polygon(xCoords, yCoords, nPoints);
    }

    @Override
    public Point getOrigin() {
        return origin;
    }

    @Override
    public void setOrigin(Point origin) {
        this.origin = origin;

        recalculate();
    }

    public float getSide() {
        return side;
    }

    public void setSide(float side) {
        this.side = side;

        recalculate();
    }

    @Override
    public Color getColor() {
        return color;
    }

    @Override
    public void setColor(Color color) {
        this.color = color;
    }

    @Override
    public void paintComponent(Graphics g) {
        g.setColor(this.color);
        g.fillPolygon(poly);
    }
}

Square.java

package com.daema.P5;


import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.geom.Point2D;

public class Square implements Shape {
    private Point origin;
    private float side;
    private Color color;

    private Polygon poly;

    public Square(int x, int y, float side, Color color) {
        this(new Point(x, y), side, color);
    }

    public Square(Point origin, float side, Color color) {
        this.origin = origin;
        this.side = side;
        this.color = color;

        recalculate();
    }

    protected void recalculate() {
        this.poly = createPolygon((float) origin.getX(), (float) origin.getY(), side);
    }

    protected Polygon createPolygon(float x, float y, float side) {
        float xOff = side / 2f;

        return createPolygon(new Point2D.Float[] {
                new Point2D.Float(x - xOff, y + xOff),  // Top Left Corner
                new Point2D.Float(x + xOff, y + xOff),  // Top Right Corner
                new Point2D.Float(x + xOff, y - xOff),  // Bottom Right Corner
                new Point2D.Float(x - xOff, y - xOff),  // Bottom Left Corner

        });
    }

    protected Polygon createPolygon(Point2D.Float[] points) {
        int nPoints = points.length + 1;
        int[] xCoords = new int[nPoints];
        int[] yCoords = new int[nPoints];

        for (int i = 0; i < nPoints; i++) {
            xCoords[i] = (int) points[i % points.length].x;
            yCoords[i] = (int) points[i % points.length].y;
        }

        return new Polygon(xCoords, yCoords, nPoints);
    }

    @Override
    public Point getOrigin() {
        return origin;
    }

    @Override
    public void setOrigin(Point origin) {
        this.origin = origin;

        recalculate();
    }

    public float getSide() {
        return side;
    }

    public void setSide(float side) {
        this.side = side;

        recalculate();
    }

    @Override
    public Color getColor() {
        return color;
    }

    @Override
    public void setColor(Color color) {
        this.color = color;
    }

    @Override
    public void paintComponent(Graphics g) {
        g.setColor(this.color);
        g.fillPolygon(poly);
    }
}

ColorUtils.java

package com.daema.P5;

import java.awt.Color;
import java.util.Random;

public class ColorUtils {
    private static final Random RAND;

    static {
        RAND = new Random(System.currentTimeMillis());
    }

    public static Color randHue(float saturation, float brightness) {
        return Color.getHSBColor(RAND.nextFloat(), saturation, brightness);
    }
}

Also I would like to add a CLEAR button somewhere that will clear my drawing panel.

Daniel Rad
  • 38
  • 5
  • 2
    Your code is too long for someone to look at it, narrow it down to a [mre], if you're trying to draw different shapes, you can take [this answer](https://stackoverflow.com/a/62763643/2180785) as a start point, or perhaps [this one](https://stackoverflow.com/a/41944799/2180785) and maybe [this one](https://stackoverflow.com/a/56385778/2180785) as well – Frakcool Apr 26 '21 at 14:39
  • 1
    You could add a new `Shape` to an `ArrayList` every time you select a new shape, and have this `Shape` have its own color, iterate this array, set the color on the `paintComponent` and when you want to clear everything just remove all the shapes from the `ArrayList` and repaint. That's basically how the first answer in my comment above is doing it, and the third one allows you to select the shape with a button and changes its color – Frakcool Apr 26 '21 at 14:46

1 Answers1

1

I think you should try to use the MVC desing pattern so that your code is easier to understand and easier to debug. Look at some examples but it roughly means to split a java swing program into three modules: Model, View and Controller.

In your case, I believe your project's structure should be as follows:

 Main.java
    - View folder
          MyView.java
          DrawPanel.java
          Circle.java
          Triangle.java
          Square.java
    - Controller folder
          MyActionListener.java
          MyMouseListener.java
    - Model folder
          Shape.java
    - Utils folder
          ColorUtils.java

With these changes we can now work on your problem. Note that you should remove the ActionListener that you've implemented in the DrawPanel.java class and place it in a new class under Controller and that the current code on your Main.java should be in the MyView.java class.

To register the changes done to the radio buttons you need the ActionListener as explained in the Oracle examples, specifically in the How to use Radio Buttons section.

To clear the view, you should create a JButton also controlled with an ActionListener.

How to control radio buttons/buttons with ActionListener and why I recomended you split the View from the Main file:

Declare the view and the logic in the main and pass a reference to the view of the controllers in order to bind them to your radio buttons/buttons as follows:

public class Main {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                View view = new View();
                MyActionListener myActionListener = new MyActionListener();
                MyMouseListener myMouseListener = new MyMouseListener();
                view.registerActionListener(myActionListener);
                view.registerMouseListener(myMouseListener);
            }
        });
    }
}

Now you should simply attach the listeners to the desired JFrame component in the view:

public class View extends JFrame{
    private JButton clearShapes;

    private void foo(){
        ...
        clearShapes = new JButton("Clear shapes");
        f.add(clearShapes);
        ...
    }

    public void registerActionListener(MyActionListener myActionListener){
        clearShapes.addActionListener(myActionListener);
        yourRadioButtons.addActionListener(myActionListener);
    }

    public void registerMouseListener(MyMouseListener myMouseListener){
        somePanel.addMouseListener(myMouseListener);
    }
}

To summarize:

  1. Clearly distinguish between Model, View and Controller (in your case mostly between View and Controller).
  2. Register each view's component with it's corresponding controller and manage their behaviour modullarly.
  3. Once each component can be managed, look at the documentation's examples on how to manage a radio button, a button or anything else and act accordingly to adapt the code to your needs.

It's a long answer that explains more than you asked for, but your code is a mess and for this project, and future projects, it's good that you implement some sort of design patters so that the code is clear to you (even if you come back after some time without working on it) and is clear to others so that they might provide help easily.

As a conclusion, your problem is not complicated, but your current structure makes it complicated. Clean it up a bit and you'll manage.

afarre
  • 512
  • 6
  • 18
  • 1
    Thank you very much. Thanks for taking the time to give me such a long and explicit answer. Very much appreciated! – Daniel Rad Apr 27 '21 at 07:45