Related
I know its a lot of code but I could really use some help identifying my problem. Thank you!
I am just now being introduced to inheritance and am challenging myself with this application to a painting program. The program is meant to draw different shapes (lines, oval, rectangles, etc.) and I used trial and error to get what I have so far. I no longer am getting errors but the program only shows a black screen. Changing colors doesn’t seem to do anything so I think it has something to do with my color variable.
This is the main class:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import java.io.*;
class Main extends JFrame implements ActionListener
{
// the draw panel
JPanel content;
CustomPanel contentCustom;
String filename = "";
File file = null;
// Menu Bar
JMenuBar bar;
// file menu
JMenu fileMenu;
JMenuItem openItem;
JMenuItem saveItem;
JMenuItem exitItem;
// mode menu
JMenu modeMenu;
JMenuItem lineItem;
JMenuItem rectItem;
JMenuItem ellipseItem;
JMenuItem freeItem;
// color menu
JMenu colorMenu;
JMenuItem colorItem;
public Main()
{
super("Paint - Line Mode Color="+Color.BLUE );
setSize(800, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// create the menu bar
bar = new JMenuBar();
// create the menus
fileMenu = new JMenu("File");
fileMenu.setMnemonic('F'); // Alt F
modeMenu = new JMenu("Mode");
colorMenu = new JMenu("Color");
// **** create the menu items
// file menu items
saveItem = new JMenuItem("Save");
saveItem.setMnemonic('s');
saveItem.addActionListener(this);
openItem = new JMenuItem("Open");
openItem.setMnemonic('o');
openItem.addActionListener(this);
exitItem = new JMenuItem("Exit");
exitItem.setMnemonic('x');
exitItem.addActionListener(this);
// mode menu items
lineItem = new JMenuItem("Line");
lineItem.addActionListener(this);
rectItem = new JMenuItem("Rectangle");
rectItem.addActionListener(this);
ellipseItem = new JMenuItem("Ellipse");
ellipseItem.addActionListener(this);
freeItem = new JMenuItem("Free Draw");
freeItem.addActionListener(this);
// color menu items
colorItem = new JMenuItem("Color");
colorItem.addActionListener(this);
// **** add menu items to the menus
// fileMenu
fileMenu.add(openItem);
fileMenu.add(saveItem);
fileMenu.addSeparator();
fileMenu.add(exitItem);
// modeMenu
modeMenu.add(lineItem);
modeMenu.add(rectItem);
modeMenu.add(ellipseItem);
modeMenu.add(freeItem);
// colorMenu
colorMenu.add(colorItem);
// **** add the menus to the bar
bar.add(fileMenu);
bar.add(modeMenu);
bar.add(colorMenu);
// **** set the bar to be the menu bar
setJMenuBar(bar);
// Container content = getContentPane(); replace this with our own
content = new CustomPanel();
contentCustom = (CustomPanel) content;
FlowLayout lay = new FlowLayout(FlowLayout.LEFT);
content.setLayout(lay);
setContentPane(content);
setVisible(true); // always do this last
}
public void actionPerformed(ActionEvent e)
{
Object source = e.getSource();
if (source == exitItem)
{
System.exit(0);
}
else if (source == lineItem)
{
contentCustom.setMyMode("Line");
setTitle("Paint - " + ((CustomPanel) content).getMyMode() + " Mode Color="+
((CustomPanel) content).getColor()
);
}
else if (source == rectItem)
{
((CustomPanel) content).setMyMode("Rectangle");
setTitle("Paint - " + ((CustomPanel) content).getMyMode() + " Mode Color="+
((CustomPanel) content).getColor()
);
}
else if (source == ellipseItem)
{
((CustomPanel) content).setMyMode("Ellipse");
setTitle("Paint - " + ((CustomPanel) content).getMyMode() + " Mode Color="+
((CustomPanel) content).getColor()
);
}
else if (source == freeItem)
{
((CustomPanel) content).setMyMode("Free");
setTitle("Paint - " + ((CustomPanel) content).getMyMode() + " Mode Color="+
((CustomPanel) content).getColor()
);
}
else if (source == colorItem)
{
Color color = Color.black;
Color newColor = JColorChooser.showDialog(this,"Draw Color",color);
if (newColor != null)
((CustomPanel) content).setColor(newColor);
setTitle("Paint - " + ((CustomPanel) content).getMyMode() + " Mode Color="+
((CustomPanel) content).getColor()
);
}
else if (source == saveItem)
{
try
{
JFileChooser fileChooser = null;
int result = JFileChooser.APPROVE_OPTION;
if (filename.equals(""))
{
fileChooser = new JFileChooser();
result = fileChooser.showSaveDialog(this);
}
if (result == JFileChooser.APPROVE_OPTION)
{
if (filename.equals(""))
{
filename = fileChooser.getSelectedFile().getName();
file = fileChooser.getSelectedFile();
}
// use contentCustom to reference the CustomPanel variables
FileWriter fw=null;
BufferedWriter bw=null;
try
{
fw = new FileWriter(file);
bw = new BufferedWriter(fw);
for (int j=0; j < contentCustom.myList.size(); j++)
{
AbstractShapeObject myShape = contentCustom.myList.get(j);
String outLine = myShape.toString();
bw.write(outLine);
bw.newLine();
}
}
catch (Exception e1)
{
}
finally
{
try
{
bw.close();
}
catch (Exception e2)
{
}
}
}
else if (result == JFileChooser.CANCEL_OPTION)
{
}
}
catch (Exception ee)
{
}
}
else if (source == openItem)
{
try
{
JFileChooser fileChooser = new JFileChooser();
int result = fileChooser.showOpenDialog(this);
if (result == JFileChooser.APPROVE_OPTION)
{
file = fileChooser.getSelectedFile();
if (file != null)
{
filename = fileChooser.getSelectedFile().getName();
contentCustom.myList.clear();
FileReader fr=null;
BufferedReader br=null;
try
{
fr = new FileReader(file);
br = new BufferedReader(fr);
String s = br.readLine();
while (s != null)
{
StringTokenizer st = new StringTokenizer(s,",");
String mode = st.nextToken();
if (mode.equals("Line"))
{
int x1 = Integer.parseInt(st.nextToken());
int y1 = Integer.parseInt(st.nextToken());
int x2 = Integer.parseInt(st.nextToken());
int y2 = Integer.parseInt(st.nextToken());
int red = Integer.parseInt(st.nextToken());
int green = Integer.parseInt(st.nextToken());
int blue = Integer.parseInt(st.nextToken());
Color color = new Color(red,green,blue);
AbstractShapeObject myShape = new Line(x1,y1,x2,y2,color);
myShape.setMode("Line");
myShape.setKeep(true);
contentCustom.myList.add(myShape);
}
else if (mode.equals("Rectangle"))
{
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
int width = Integer.parseInt(st.nextToken());
int height = Integer.parseInt(st.nextToken());
int red = Integer.parseInt(st.nextToken());
int green = Integer.parseInt(st.nextToken());
int blue = Integer.parseInt(st.nextToken());
Color color = new Color(red,green,blue);
AbstractShapeObject myShape = new Rectangle(x,y,width,height,color);
myShape.setMode("Rectangle");
myShape.setKeep(true);
contentCustom.myList.add(myShape);
}
else if (mode.equals("Ellipse"))
{
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
int width = Integer.parseInt(st.nextToken());
int height = Integer.parseInt(st.nextToken());
int red = Integer.parseInt(st.nextToken());
int green = Integer.parseInt(st.nextToken());
int blue = Integer.parseInt(st.nextToken());
Color color = new Color(red,green,blue);
AbstractShapeObject myShape = new Ellipse(x,y,width,height,color);
myShape.setMode("Rectangle");
myShape.setKeep(true);
contentCustom.myList.add(myShape);
}
s = br.readLine();
} // end of while loop
contentCustom.repaint();
}
catch (Exception e1)
{
}
finally
{
try
{
br.close();
}
catch (Exception e2)
{
}
}
}
}
else if (result == JFileChooser.CANCEL_OPTION)
{
}
}
catch (Exception ee)
{
}
}
}
public static void main(String[] args)
{
Main paint = new Main();
}
} // end of class Main
This is the the AbstractShapeObject Class:
public abstract class AbstractShapeObject implements ShapeInterface
{
private int x;
private int y;
private int width;
private int height;
private Color color;
private int thickness;
private String mode; // Am I a Rectangle, Line, etc.
private boolean keep; // do I keep the shape or erase it later
public AbstractShapeObject()
{
this(0,0,0,0,defaultThickness,defaultColor);
}
public AbstractShapeObject(int x, int y)
{
this(x,y,defaultWidth,defaultHeight,defaultThickness,defaultColor);
}
public AbstractShapeObject(int x, int y, Color color)
{
this(x,y,defaultWidth,defaultHeight,defaultThickness,color);
}
public AbstractShapeObject(int x, int y, int thickness)
{
this(x,y,defaultWidth,defaultHeight,thickness,defaultColor);
}
public AbstractShapeObject(int x, int y, int thickness, Color color)
{
this(x,y,defaultWidth,defaultHeight,thickness,color);
}
public AbstractShapeObject(int x, int y, int width, int height)
{
this(x,y,width,height,defaultThickness,defaultColor);
}
public AbstractShapeObject(int x, int y, int width, int height, Color color)
{
this(x,y,width,height,defaultThickness,color);// call this(?????) (the constructor with all the parameters below)
}
public AbstractShapeObject(int x, int y, int width, int height, int thickness)
{
this(x,y,width,height,thickness,defaultColor);// call this(?????) (the constructor with all the parameters below)
}
public AbstractShapeObject(int x, int y, int width, int height, int thickness, Color color)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
this.thickness = thickness;
mode = "None";
keep = true;
}
// write all of your getter and setter methods here !!!
// do NOT write your draw methods here
// you will write your draw methods in the specific classes
// since you don't really know what to draw here
public int getX()
{
return x; //change me
}
public void setX(int x)
{this.x = x;
}
public int getY()
{
return y; //change me
}
public void setY(int y)
{this.y = y;
}
public int getWidth()
{
return width; //change me
}
public void setWidth(int width)
{this.width = width;
}
public int getHeight()
{
return height; //change me
}
public void setHeight(int height)
{this.height = height;
}
public int getThickness()
{
return thickness; //change me
}
public void setThickness(int thickness)
{this.thickness = thickness;
}
public Color getColor()
{
// ************* change this to ????? **************
// What are you getting?
return color; //change me!!!!!!!!!!
}
public void setColor(Color color)
{this.color = color;
}
public String getMode()
{
return mode; // change me
}
public void setMode(String mode)
{this.mode = mode;
}
public boolean getKeep()
{
return keep; // change me
}
public void setKeep(boolean keep)
{this.keep = keep;
}
// do NOT change this method
public void calcXYWidthHeight(int x1, int y1, int x2, int y2)
{
int width = Math.abs(x2-x1);
int height = Math.abs(y2-y1);
// now I need to find my upper left corner point
// find the x1 value
if (x2 <= x1)
{
x1 = x2;
}
// find the y1 value
if (y2 <= y1) // they have dragged the mouse up
{
y1 = y2;
}
// now set the instance variables of the class
setX(x1);
setY(y1);
setWidth(width);
setHeight(height);
}
} // end of class AbstractShapeObject
This is one of the shape classes I coded. All of them have the same format but this one is for drawing an ellipse:
import java.awt.*;
import java.awt.geom.*;
public class Ellipse extends AbstractShapeObject {
public Ellipse(int x, int y, int width, int height) {
super(x,y,width,height);
}
public Ellipse(int x, int y, int width, int height, Color color) {
super(x,y,width,height,color);// call the appropriate super constructor
}
public Ellipse(int x, int y, int width, int height, int thickness) {
super(x,y,width,height,thickness);// call the appropriate super constructor
}
public Ellipse(int x, int y, int width, int height, int thickness, Color color) {
super(x,y,width,height,thickness,color);// call the appropriate super constructor
}
public void draw(Graphics window)
{
// first, set your draw color
Color drawColor = window.getColor();
window.setColor(drawColor);// window.????(????);
// second, draw the shape (i.e. draw the ellipse or Oval)
window.drawOval(getX(),getY(),getWidth(),getHeight());// window.????(????);
}
// write the toString() method
// use the same format that is used for the Rectangle and Line classes
public String toString()
{
return getMode() + "," + getX() + "," + getY() + "," + getWidth() + "," + getHeight() + ","
+ getColor().getRed() + "," + getColor().getGreen() + "," + getColor().getBlue();
}
} // end of class Ellipse
This is the CustomPanel Class (this was provided to us so I don’t think its an error here):
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;
import java.awt.image.BufferedImage;
// this JPanel listens for the mouse events
public class CustomPanel extends JPanel implements MouseListener, MouseMotionListener
{
// for buffering
private BufferedImage back;
ArrayList <AbstractShapeObject> myList = new ArrayList<AbstractShapeObject>();
String myMode = "Line"; // None, Line, Rectangle, etc.
Color color = Color.blue; // current draw color
int x1 = 0;
int y1 = 0;
int x2 = 0;
int y2 = 0;
boolean drawing = false; // are we drawing with the mouse?
public CustomPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
setBackground(Color.red);
}
public void setMyMode(String mode)
{
myMode = mode;
}
public String getMyMode()
{
return myMode;
}
public void setColor(Color color)
{
this.color = color;
}
public Color getColor()
{
return color;
}
public void removeTempShapes()
{
// get rid of temp objects
for (int j=0; j < myList.size(); j++)
{
AbstractShapeObject myShape = myList.get(j);
if (!myShape.getKeep())
{
myList.remove(j);
}
}
}
public void update(Graphics window)
{
paintComponent(window);
}
public void paintComponent(Graphics window)
{
super.paintComponent((Graphics2D)window);
Graphics2D g2 = (Graphics2D) window;
//take a snap shop of the current screen and same it as an image
//that is the exact same width and height as the current screen
back = (BufferedImage)(createImage(getWidth(),getHeight()));
//create a graphics reference to the back ground image
//we will draw all changes on the background image
Graphics gMemory = back.createGraphics();
// clear the screen
gMemory.setColor(Color.BLACK);
gMemory.fillRect(0,0,getWidth(),getHeight());
// draw all the objects
for (int j=0; j < myList.size(); j++)
{
AbstractShapeObject myShape = myList.get(j);
myShape.draw(gMemory);
}
// *** show the screen by copying the image to the graphics display ********
g2.drawImage(back, null, 0, 0);
}
/* mouse motion events... */
public void mouseMoved(MouseEvent event)
{
// DO NOT DO ANYTHING HERE
}
public void mouseDragged(MouseEvent event)
{
if (drawing)
{
if (myMode.equals("Line"))
{
x2 = event.getX();
y2 = event.getY();
// get rid of temp objects
removeTempShapes();
AbstractShapeObject myShape = new Line(x1,y1,x2,y2,color);
myShape.setMode("Line");
myShape.setKeep(false);
myList.add(myShape);
repaint();
} // end of if (myMode.equals("Line"))
else if (myMode.equals("Free"))
{
// update the from coordinates
x1 = x2;
y1 = y2;
// get the draw to coordinates
x2 = event.getX();
y2 = event.getY();
AbstractShapeObject myShape = new Line(x1,y1,x2,y2,color);
myShape.setMode("Free");
myShape.setKeep(true);
myList.add(myShape);
repaint();
} // end of if (myMode.equals("Line"))
else if (myMode.equals("Rectangle"))
{
// get the draw to coordinates (lower right)
x2 = event.getX();
y2 = event.getY();
// get rid of temp objects
removeTempShapes();
AbstractShapeObject myShape = new Rectangle(x1,y1,x2,y2,color);
myShape.calcXYWidthHeight(x1,y1,x2,y2);
myShape.setMode("Rectangle");
myShape.setKeep(false);
myList.add(myShape);
repaint();
} // end of if (myMode.equals("Line"))
else if (myMode.equals("Ellipse"))
{
// get the draw to coordinates (lower right)
x2 = event.getX();
y2 = event.getY();
// get rid of temp objects
removeTempShapes();
AbstractShapeObject myShape = new Ellipse(x1,y1,x2,y2,color);
myShape.calcXYWidthHeight(x1,y1,x2,y2);
myShape.setMode("Ellipse");
myShape.setKeep(false);
myList.add(myShape);
repaint();
} // end of if (myMode.equals("Ellipse"))
} // end of if (drawing)
}
/* mouse events... */
public void mouseClicked(MouseEvent event)
{
// DO NOT DO ANYTHING HERE
}
public void mousePressed(MouseEvent event)
{
drawing = true;
// save the original coordinates
x1 = event.getX();
y1 = event.getY();
// save where to draw to
x2 = event.getX();
y2 = event.getY();
}
public void mouseReleased(MouseEvent event)
{
if (drawing)
{
if (myMode.equals("Line"))
{
// get rid of temp objects
removeTempShapes();
x2 = event.getX();
y2 = event.getY();
AbstractShapeObject myShape = new Line(x1,y1,x2,y2,color);
myShape.setMode("Line");
myShape.setKeep(true);
myList.add(myShape);
}
else if (myMode.equals("Free"))
{
// nothing to do since it has already been drawn
}
else if (myMode.equals("Rectangle"))
{
// get rid of temp objects
removeTempShapes();
x2 = event.getX();
y2 = event.getY();
AbstractShapeObject myShape = new Rectangle(x1,y1,x2,y2,color);
myShape.calcXYWidthHeight(x1,y1,x2,y2);
myShape.setMode("Rectangle");
myShape.setKeep(true);
myList.add(myShape);
}
else if (myMode.equals("Ellipse"))
{
// get rid of temp objects
removeTempShapes();
x2 = event.getX();
y2 = event.getY();
AbstractShapeObject myShape = new Ellipse(x1,y1,x2,y2,color);
myShape.calcXYWidthHeight(x1,y1,x2,y2);
myShape.setMode("Ellipse");
myShape.setKeep(true);
myList.add(myShape);
}
drawing = false;
repaint();
} // end of if (drawing)
} // end of mouseReleased
public void mouseEntered(MouseEvent event)
{
// DO NOT DO ANYTHING HERE
}
public void mouseExited(MouseEvent event)
{
// DO NOT DO ANYTHING HERE
}
} // end of class CustomPanel
This is the ShapeInterface class (This was also provided):
// DO NOT TOUCH THIS FILE
// THE INTERFACE HAS BEEN WRITTEN FOR YOU
import java.awt.Color;
import java.awt.*;
public interface ShapeInterface
{
// all variables (constants) are public static final by default
public int defaultWidth = 1;
public int defaultHeight = 1;
public int defaultThickness = 1;
public Color defaultColor = Color.RED;
// these methods are all public abstract by default
public int getX();
public void setX(int x);
public int getY();
public void setY(int y);
public int getWidth();
public void setWidth(int width);
public int getHeight();
public void setHeight(int height);
public int getThickness();
public void setThickness(int thickness);
public Color getColor();
public void setColor(Color color);
public String getMode();
public void setMode(String mode);
public boolean getKeep();
public void setKeep(boolean keep);
public abstract void draw(Graphics window);
} //end of interface ShapeInterface
I didn't check all the code, but there is an error in Ellipse class, check my remarks
public void draw(Graphics window)
{
//This is a nonsense, get the Color from the graphics and setting back the same color will do nothing
Color drawColor = window.getColor();
//Here you should set your own color, since when your code is called the Graphic's color is black
//You have a color member variable in AbstractShape class, use it here
window.setColor(drawColor);// window.????(????);
// second, draw the shape (i.e. draw the ellipse or Oval)
window.drawOval(getX(),getY(),getWidth(),getHeight());// window.????(????);
}
Hey I am trying to figure a confusion with accessing a variable's value from parent class in child class.
Scenario #1
Below is the code for the class DrawingPanel():
public class DrawingPanel extends JPanel implements MouseListener
{
private ArrayList<Balloon> balloons;
private Balloon activeBalloon;
private boolean picked;
private int offsetX, offsetY;
private double offsetR;
int rotate;
// Constructor:
public DrawingPanel()
{
setBackground(Color.WHITE);
addMouseListener(this);
balloons = new ArrayList<Balloon>();
activeBalloon = null;
picked = false;
rotate = 0;
}
// Called from controlPanel when the "Add balloon" button is clicked.
// Places a new balloon with a random radius and the current color
// at the center of the drawing panel.
public void addBalloon(int shape)
{
int w = getWidth();
int h = getHeight();
int radius = 10 + (int) (Math.random() * w / 2);
switch (shape)
{
case 1:
activeBalloon = new SquareBalloon(w / 2, h / 2, radius);
break;
default:
activeBalloon = new SquareBalloon(w / 2, h / 2, radius);
break;
}
balloons.add(activeBalloon);
repaint();
}
// Repaints this panel. If activeBalloon is set, paints it on top.
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Balloon b : balloons)
{
if (!picked || b != activeBalloon)
{
b.draw(g, true);
}
}
if (picked && activeBalloon != null)
activeBalloon.draw(g, false);
}
// Called when the mouse is clicked on the drawing panel.
// If inside a balloon, makes it "active", remembers the offsets
// of the click from the center.
// If on the border of a balloon, makes it "active", remembers the
// distance of the click from the center.
#Override
public void mousePressed(MouseEvent e)
{
// Value for parameter rotate is set here on mouse click
rotate = 2;
int x = e.getX();
int y = e.getY();
picked = false;
for (int k = balloons.size() - 1; k >= 0 && !picked; k--)
{
Balloon b = balloons.get(k);
if (b.isInside(x, y))
{
picked = true;
offsetX = x - b.getX();
offsetY = y - b.getY();
activeBalloon = b;
} else if (b.isOnBorder(x, y))
{
picked = true;
offsetR = b.distance(x, y) - b.getRadius();
activeBalloon = b;
}
}
if (picked)
repaint();
}
// "Drops" the picked balloon, if any.
#Override
public void mouseReleased(MouseEvent e)
{
}
// Not used:
#Override
public void mouseEntered(MouseEvent e)
{
}
#Override
public void mouseExited(MouseEvent e)
{
}
#Override
public void mouseClicked(MouseEvent e)
{
}
}
Below is the SquareBalloon() class which inherits the parent class Balloon. The Balloon class inherits the DrawingPanel() class.
public class SquareBalloon extends Balloon implements MouseListener
{
public SquareBalloon()
{
}
/**
* Constructs a balloon with a given center, radius and color
*
* #param x
* x-coordinate of the center
* #param y
* y-coordinate of the center
* #param r
* radius of the balloon
*/
public SquareBalloon(int x, int y, int r)
{
super(x, y, r);
}
/**
* Draws a solid circle if makeItFilled is true and outline only if
* makeItFilled is false
*
* #param g
* graphics context
* #param makeItFilled
* draws a solid circle if true
*/
#Override
public void draw(Graphics g, boolean makeItFilled)
{
Graphics2D g2d = (Graphics2D) g;
g.setColor(color);
System.out.println("This is the value of rotate" + rotate + "from the draw() method of SquareBalloon() class.");
if (makeItFilled)
{
Rectangle rect2 = new Rectangle(xCenter - radius, yCenter - radius, radius, radius);
if (rotate == 2)
{
System.out.println("This is the value of rotate" + rotate
+ "from the makedItFilled if statement in draw() method of SquareBalloon() class.");
g2d.rotate(45 * (Math.PI / 180));
}
g2d.fill(rect2);
} else
{
Rectangle rect2 = new Rectangle(xCenter - radius, yCenter - radius, radius, radius);
if (rotate == 2)
{
System.out.println("This is the value of rotate" + rotate
+ "from the other if statement in draw() method of SquareBalloon() class.");
g2d.rotate(45 * (Math.PI / 180));
}
g2d.draw(rect2);
}
}
}
Scenario #2
Below is the code for the class DrawingPanel():
public class DrawingPanel extends JPanel implements MouseListener
{
private ArrayList<Balloon> balloons;
private Balloon activeBalloon;
private boolean picked;
private int offsetX, offsetY;
private double offsetR;
int rotate;
// Constructor:
public DrawingPanel()
{
setBackground(Color.WHITE);
addMouseListener(this);
balloons = new ArrayList<Balloon>();
activeBalloon = null;
picked = false;
rotate = 0;
}
// Called from controlPanel when the "Add balloon" button is clicked.
// Places a new balloon with a random radius and the current color
// at the center of the drawing panel.
public void addBalloon(int shape)
{
int w = getWidth();
int h = getHeight();
int radius = 10 + (int) (Math.random() * w / 2);
switch (shape)
{
case 1:
activeBalloon = new SquareBalloon(w / 2, h / 2, radius);
break;
default:
activeBalloon = new SquareBalloon(w / 2, h / 2, radius);
break;
}
balloons.add(activeBalloon);
repaint();
}
// Repaints this panel. If activeBalloon is set, paints it on top.
#Override
public void paintComponent(Graphics g)
{
// to restore original angle of balloon objects
if (rotate > 2)
rotate = 0;
super.paintComponent(g);
for (Balloon b : balloons)
{
if (!picked || b != activeBalloon)
{
b.draw(g, true, rotate);
}
}
if (picked && activeBalloon != null)
activeBalloon.draw(g, false, rotate);
}
// Called when the mouse is clicked on the drawing panel.
// If inside a balloon, makes it "active", remembers the offsets
// of the click from the center.
// If on the border of a balloon, makes it "active", remembers the
// distance of the click from the center.
#Override
public void mousePressed(MouseEvent e)
{
// Value for parameter rotate is set here on mouse click
rotate++;
int x = e.getX();
int y = e.getY();
picked = false;
for (int k = balloons.size() - 1; k >= 0 && !picked; k--)
{
Balloon b = balloons.get(k);
if (b.isInside(x, y))
{
picked = true;
offsetX = x - b.getX();
offsetY = y - b.getY();
activeBalloon = b;
} else if (b.isOnBorder(x, y))
{
picked = true;
offsetR = b.distance(x, y) - b.getRadius();
activeBalloon = b;
}
}
if (picked)
repaint();
}
// "Drops" the picked balloon, if any.
#Override
public void mouseReleased(MouseEvent e)
{
}
// Not used:
#Override
public void mouseEntered(MouseEvent e)
{
}
#Override
public void mouseExited(MouseEvent e)
{
}
#Override
public void mouseClicked(MouseEvent e)
{
}
}
Below is the SquareBalloon() class which inherits the parent class Balloon. The Balloon class inherits the DrawingPanel() class.
public class SquareBalloon extends Balloon implements MouseListener
{
public SquareBalloon()
{
}
/**
* Constructs a balloon with a given center, radius and color
*
* #param x
* x-coordinate of the center
* #param y
* y-coordinate of the center
* #param r
* radius of the balloon
*/
public SquareBalloon(int x, int y, int r)
{
super(x, y, r);
}
/**
* Draws a solid circle if makeItFilled is true and outline only if
* makeItFilled is false
*
* #param g
* graphics context
* #param makeItFilled
* draws a solid circle if true
* #param rotate
* receives rotate variable value set in mousePressed() method of
* DrawingPanel() class
*/
#Override
public void draw(Graphics g, boolean makeItFilled, int rotate)
{
Graphics2D g2d = (Graphics2D) g;
g.setColor(color);
System.out.println("This is the value of rotate" + rotate + "from the draw() method of SquareBalloon() class.");
if (makeItFilled)
{
Rectangle rect2 = new Rectangle(xCenter - radius, yCenter - radius, radius, radius);
if (rotate == 2)
{
System.out.println("This is the value of rotate" + rotate
+ "from the makedItFilled if statement in draw() method of SquareBalloon() class.");
g2d.rotate(45 * (Math.PI / 180));
}
g2d.fill(rect2);
} else
{
Rectangle rect2 = new Rectangle(xCenter - radius, yCenter - radius, radius, radius);
if (rotate == 2)
{
System.out.println("This is the value of rotate" + rotate
+ "from the other if statement in draw() method of SquareBalloon() class.");
g2d.rotate(45 * (Math.PI / 180));
}
g2d.draw(rect2);
}
}
}
Can someone please explain why in Scenario #1, the change in value of variable int rotate after the mousePressed method initiates is not being passed to the subclasses of Balloon class so that the if statements with condition rotate == 2 in the draw method of class SquareBalloon executes? Because in Scenario #2 it does work.
Thank you!
So I have two classes here:
PhotoComponent class:
(This class is to handle a specific image as a JComponent. When "flipped" I want to draw pen strokes instead of having an image. So I replace the image with a rectangle, attempting to draw pen strokes over it.)
public class PhotoComponent extends JComponent {
private Image pic;
private boolean flipped;
private int contentAreaWidth;
private int contentAreaHeight;
p
#Override
public void paintComponent(Graphics g) {
//Does all the drawing and contains whatever state information is associated with the photo
//create an action event to auto call repaint
//call repaint anytime flip was changed to true or false
System.out.println("Draw: " + draw + ", Pic: " + pic);
if (draw && pic != null) {
super.paintComponent(g);
System.out.println("width using this: " + this.getWidth() + ", actual width of JPanel: " + contentAreaWidth);
System.out.println("height using this: " + this.getHeight() + ", actual height of JPanel: " + contentAreaHeight);
g2 = (Graphics2D) g;
int x = (contentAreaWidth - pic.getWidth(null)) / 2;
int y = (contentAreaHeight - pic.getHeight(null)) / 2;
if (!flipped) {
g2.drawImage(pic, x, y, null);
} else if (flipped) {
g2.setColor(Color.WHITE);
g2.fillRect(x,y,pic.getWidth(null), pic.getHeight(null));
g2.drawRect(x, y, pic.getWidth(null), pic.getHeight(null));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (drawingMode) {
g2.setPaint(Color.RED);
if (drawOval) {
penStrokes.put(ovalX, ovalY);
if (penStrokes != null) {
for (Integer xCoor : penStrokes.keySet()) {
g2.setPaint(Color.RED);
int brushSize = 5;
g2.fillOval((xCoor - (brushSize / 2)), (penStrokes.get(xCoor) - (brushSize / 2)), brushSize, brushSize);
//System.out.println("SIZE OF HASHTABLE: " + penStrokes.size());
}
}
System.out.println("Filling an oval!" + ovalX + ", " + ovalY);
}
} else if (textMode) {
g2.setPaint(Color.YELLOW);
if (drawRect) {
rectDimensions.add(rectX);
rectDimensions.add(rectY);
rectDimensions.add(rectWidth);
rectDimensions.add(rectHeight);
for (int i = 0; i < rectDimensions.size(); i+=4) {
g2.fillRect(rectDimensions.get(i), rectDimensions.get(i+1), rectDimensions.get(i+2), rectDimensions.get(i+3));
g2.drawRect(rectDimensions.get(i), rectDimensions.get(i+1), rectDimensions.get(i+2), rectDimensions.get(i+3));
}
}
}
System.out.println("This is being called again!");
}
}
}
public void setRectangle(int x, int y, int width, int height) {
drawRect = true;
rectX = x;
rectY = y;
rectWidth = width;
rectHeight = height;
}
public void removeRectangle() {
drawRect = false;
}
public int[] setOval(int currentX, int currentY) {
drawOval = true;
int[] toReturn = {ovalX, ovalY};
ovalX =
NOTE THE DRAWLINE() METHOD ABOVE. I am drawing at the given points, repainting, and setting the old variables to be the current variables.
Main class:
private static PhotoComponent img;
private static JFrame frame;
private static JPanel contentArea;
//Mouse Coordinates for PenStrokes
private static int oldX, oldY;
//Mouse Coordinates for Sticky Notes
private static Point clickPoint;
public static void main (String[] args) {
frame = new JFrame("PhotoManip");
img = null;
contentArea = null;
oldX = 0;
oldY = 0;
setupMenubar(frame);
setupJFrame(frame);
}
private static void addPhotoComponent(File file) {
}
if (img.getTextMode()) {
img.removeRectangle();
clickPoint = null;
}
}
});
img.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
if (img.getDrawingMode()) {
if (withinRange(e.getX(), e.getY())) {
int[] toUpdate = img.setOval(e.getX(), e.getY());
oldX = toUpdate[0];
oldY = toUpdate[1];
img.repaint();
}
}
if (img.getTextMode()) {
if (withinRange(e.getX(), e.getY())) {
Point dragPoint = e.getPoint();
h, height);
img.repaint();
}
}
}
});
if (img!=null) {
contentArea.add(img);
}
}
private static boolean withinRange(int x, int y) {
if (x > img.getX() && x < img.getX() + img.getWidth()) {
if (y > img.getY() && y < img.getY() + img.getHeight()) {
return true;
}
}
return false;
}
private static void flipImage() {
if (!img.isFlipped()) {
img.setFlipped(true);
} else if (img.isFlipped()) {
img.setFlipped(false);
}
}
drawLine() is called above in this main class, when a mousedrag occurs. Problem is that the strokes don't appear to show.
I know that the program is calling g2.fillOval() because I am printing out a verification statement afterwards.
Additionally, I have created print statements for when the mouse is pressed and dragged and they are getting the correct coordinates?
Why don't red strokes appear? I'm confused. Is it the way my code is structured?
The crux of your problem is that you are trying to draw something outside the paintComponent method, which is never supported. Whatever you draw will get overwritten by the next call of paintComponent, which will happen almost instantly. We can solve this by storing the co-ordinates of the oval and drawing it within paintComponent instead of trying to draw on a graphics object outside of the paintComponent method. See code below:
First we are going to add the following variables to your PhotoComponent class:
private boolean drawOval = false;
private int ovalX = 0;
private int ovalY = 0;
Then we will add methods for controlling them:
public int[] setOval(int currentX, int currentY) {
drawOval = true;
int[] toReturn = {ovalX, ovalY};
ovalX = currentX;
ovalY = currentY;
return toReturn;
}
public void removeOval() {
drawOval = false;
}
After that we can change the paintComponent method to have it draw the oval based on those variables:
#Override
public void paintComponent(Graphics g) {
//Does all the drawing and contains whatever state information is associated with the photo
//create an action event to auto call repaint
//call repaint anytime flip was changed to true or false
super.paintComponent(g);
g2 = (Graphics2D) g;
int x = (contentAreaWidth - pic.getWidth(null)) / 2;
int y = (contentAreaHeight - pic.getHeight(null)) / 2;
if (!flipped) {
g2.drawImage(pic, x, y, null);
} else if (flipped) {
g2.setColor(Color.WHITE);
g2.fillRect(x, y, pic.getWidth(null), pic.getHeight(null));
g2.drawRect(x, y, pic.getWidth(null), pic.getHeight(null));
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.RED);
}
//took the code you already used for drawing the oval and moved it here
if (drawOval) {
g2.setPaint(Color.RED);
int brushSize = 5;
g2.fillOval((ovalX - (brushSize / 2)), (ovalY - (brushSize / 2)), brushSize, brushSize);
}
}
Finally change the addPhotoComponent method to update those variables instead of trying to draw the oval directly:
private static void addPhotoComponent(File file) {
Image image = null;
try {
image = ImageIO.read(file);
} catch (IOException e2) {
System.out.println("ERROR: Couldn't get image.");
}
img = new PhotoComponent(image, contentArea);
img.revalidate();
img.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
// your code here
System.out.println("You flipped the photo!!!");
flipImage();
img.repaint();
}
}
#Override
public void mousePressed(MouseEvent e) {
img.setOval(e.getX(), e.getY());
}
#Override
public void mouseReleased(MouseEvent e) {
img.removeOval();
}
});
img.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
int[] toUpdate = img.setOval(e.getX(), e.getY());
oldX = toUpdate[0];
oldY = toUpdate[1];
}
});
if (img != null) {
contentArea.add(img);
}
}
I have a problem with my current animation that I'm running using Java Swing. It is a discrete event simulation and the text based simulation is working fine, I'm just having problems connecting the simulating to GUI output.
For this example I will have 10 cars to be simulated. The cars are represented by JPanels which I will elaborate on in a few moments.
So consider, the event process_car_arrival. Every time this event is scheduled for execution, I'm adding a Car object to an ArrayList called cars in my Model class. The Car class has the following relevant attributes:
Point currentPos; // The current position, initialized in another method when knowing route.
double speed; // giving the speed any value still causes the same problem but I have 5 atm.
RouteType route; // for this example I only consider one simple route
In addition it has the following method move() :
switch (this.route) {
case EAST:
this.currentPos.x -= speed;
return this.currentPos;
.
.
.
//only above is relevant in this example
This is all well. so in theory the car traverses along a straight road from east to west as I just invoke the move() method for each car I want to move.
Returning to the process_car_arrival event. After adding a Car object it invokes a method addCarToEast() in the View class. This adds a JPanel at the start of the road going from east to west.
Going to the View class now I have a ** separate** thread which does the following ( the run() method) :
#Override
public void run() {
while (true) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!cars.isEmpty()) {
cars.get(i).setLocation(
new Point(getModel.getCars().get(i).move()));
if (i == cars.size() - 1) {
i = 0;
} else {
i++;
}
}
}
}
The above does move the car from east to west smoothly at first. But after there is 3-4 cars moving it just ends up being EXTREMELY slow and when I have 10 cars moving it just ends up moving very little.
Just to clear up, at the moment in the Model class there's an ArrayList of Car objects, and in the View class there is also an ArrayList of JPanel objects representing the cars. I'm trying to match the Car objects to the JPanels, but I'm obviously doing a cra**y job.
I suspect that I'm doing something insanely inefficient but I don't know what. I thought initially maybe it's accessing the ArrayList so much which I guess would make it really slow.
Any pointers to what I can change to make it run smoothly?
Based on this previous answer, the example below simulates a fleet of three cabs moving randomly on a rectangular grid. A javax.swing.Timer drives the animation at 5 Hz. The model and view are tightly coupled in CabPanel, but the animation may provide some useful insights. In particular, you might increase the number of cabs or lower the timer delay.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
/**
* #see https://stackoverflow.com/a/14887457/230513
* #see https://stackoverflow.com/questions/5617027
*/
public class FleetPanel extends JPanel {
private static final Random random = new Random();
private final MapPanel map = new MapPanel();
private final JPanel control = new JPanel();
private final List<CabPanel> fleet = new ArrayList<CabPanel>();
private final Timer timer = new Timer(200, null);
public FleetPanel() {
super(new BorderLayout());
fleet.add(new CabPanel("Cab #1", Hue.Cyan));
fleet.add(new CabPanel("Cab #2", Hue.Magenta));
fleet.add(new CabPanel("Cab #3", Hue.Yellow));
control.setLayout(new GridLayout(0, 1));
for (CabPanel cp : fleet) {
control.add(cp);
timer.addActionListener(cp.listener);
}
this.add(map, BorderLayout.CENTER);
this.add(control, BorderLayout.SOUTH);
}
public void start() {
timer.start();
}
private class CabPanel extends JPanel {
private static final String format = "000000";
private final DecimalFormat df = new DecimalFormat(format);
private JLabel name = new JLabel("", JLabel.CENTER);
private Point point = new Point();
private JLabel position = new JLabel(toString(point), JLabel.CENTER);
private int blocks;
private JLabel odometer = new JLabel(df.format(0), JLabel.CENTER);
private final JComboBox colorBox = new JComboBox();
private final JButton reset = new JButton("Reset");
private final ActionListener listener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int ds = random.nextInt(3) - 1;
if (random.nextBoolean()) {
point.x += ds;
} else {
point.y += ds;
}
blocks += Math.abs(ds);
update();
}
};
public CabPanel(String s, Hue hue) {
super(new GridLayout(1, 0));
name.setText(s);
this.setBackground(hue.getColor());
this.add(map, BorderLayout.CENTER);
for (Hue h : Hue.values()) {
colorBox.addItem(h);
}
colorBox.setSelectedIndex(hue.ordinal());
colorBox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Hue h = (Hue) colorBox.getSelectedItem();
CabPanel.this.setBackground(h.getColor());
update();
}
});
reset.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
point.setLocation(0, 0);
blocks = 0;
update();
}
});
this.add(name);
this.add(odometer);
this.add(position);
this.add(colorBox);
this.add(reset);
}
private void update() {
position.setText(CabPanel.this.toString(point));
odometer.setText(df.format(blocks));
map.repaint();
}
private String toString(Point p) {
StringBuilder sb = new StringBuilder();
sb.append(Math.abs(p.x));
sb.append(p.x < 0 ? " W" : " E");
sb.append(", ");
sb.append(Math.abs(p.y));
sb.append(p.y < 0 ? " N" : " S");
return sb.toString();
}
}
private class MapPanel extends JPanel {
private static final int SIZE = 16;
public MapPanel() {
this.setPreferredSize(new Dimension(32 * SIZE, 32 * SIZE));
this.setBackground(Color.lightGray);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = this.getWidth();
int h = this.getHeight();
g2d.setColor(Color.gray);
for (int col = SIZE; col <= w; col += SIZE) {
g2d.drawLine(col, 0, col, h);
}
for (int row = SIZE; row <= h; row += SIZE) {
g2d.drawLine(0, row, w, row);
}
for (CabPanel cp : fleet) {
Point p = cp.point;
int x = SIZE * (p.x + w / 2 / SIZE) - SIZE / 2;
int y = SIZE * (p.y + h / 2 / SIZE) - SIZE / 2;
g2d.setColor(cp.getBackground());
g2d.fillOval(x, y, SIZE, SIZE);
}
}
}
public enum Hue {
Cyan(Color.cyan), Magenta(Color.magenta), Yellow(Color.yellow),
Red(Color.red), Green(Color.green), Blue(Color.blue),
Orange(Color.orange), Pink(Color.pink);
private final Color color;
private Hue(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
private static void display() {
JFrame f = new JFrame("Dispatch");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FleetPanel fp = new FleetPanel();
f.add(fp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
fp.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
display();
}
});
}
}
I couldn't resist...
I got 500 cars running on the screen with little slow down (it wasn't the fastest...about 200-300 was pretty good...
This uses panels to represent each vehicle. If you want to get better performance, your probably need to look at using a backing buffer of some kind.
public class TestAnimation10 {
public static void main(String[] args) {
new TestAnimation10();
}
public TestAnimation10() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
final TrackPane trackPane = new TrackPane();
JSlider slider = new JSlider(1, 500);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
trackPane.setCongestion(((JSlider)e.getSource()).getValue());
}
});
slider.setValue(5);
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(trackPane);
frame.add(slider, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TrackPane extends JPanel {
private List<Car> cars;
private int maxCars = 1;
private List<Point2D[]> points;
private Ellipse2D areaOfEffect;
public TrackPane() {
points = new ArrayList<>(25);
cars = new ArrayList<>(25);
setLayout(null);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Rectangle bounds = areaOfEffect.getBounds();
List<Car> tmp = new ArrayList<>(cars);
for (Car car : tmp) {
car.move();
if (!bounds.intersects(car.getBounds())) {
remove(car);
cars.remove(car);
}
}
updatePool();
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
updateAreaOfEffect();
}
protected void updateAreaOfEffect() {
double radius = Math.max(getWidth(), getHeight()) * 1.5d;
double x = (getWidth() - radius) / 2d;
double y = (getHeight() - radius) / 2d;
areaOfEffect = new Ellipse2D.Double(x, y, radius, radius);
}
#Override
public void invalidate() {
super.invalidate();
updateAreaOfEffect();
}
protected void updatePool() {
while (cars.size() < maxCars) {
// if (cars.size() < maxCars) {
Car car = new Car();
double direction = car.getDirection();
double startAngle = direction - 180;
double radius = areaOfEffect.getWidth();
Point2D startPoint = getPointAt(radius, startAngle);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
double x = cx + (startPoint.getX() - car.getWidth() / 2);
double y = cy + (startPoint.getY() - car.getHeight() / 2);
car.setLocation((int)x, (int)y);
Point2D targetPoint = getPointAt(radius, direction);
points.add(new Point2D[]{startPoint, targetPoint});
add(car);
cars.add(car);
}
}
#Override
public void paint(Graphics g) {
super.paint(g);
Font font = g.getFont();
font = font.deriveFont(Font.BOLD, 48f);
FontMetrics fm = g.getFontMetrics(font);
g.setFont(font);
g.setColor(Color.RED);
String text = Integer.toString(maxCars);
int x = getWidth() - fm.stringWidth(text);
int y = getHeight() - fm.getHeight() + fm.getAscent();
g.drawString(text, x, y);
text = Integer.toString(getComponentCount());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
text = Integer.toString(cars.size());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public void setCongestion(int value) {
maxCars = value;
}
}
protected static Point2D getPointAt(double radius, double angle) {
double x = Math.round(radius / 2d);
double y = Math.round(radius / 2d);
double rads = Math.toRadians(-angle);
double fullLength = Math.round((radius / 2d));
double xPosy = (Math.cos(rads) * fullLength);
double yPosy = (Math.sin(rads) * fullLength);
return new Point2D.Double(xPosy, yPosy);
}
public class Car extends JPanel {
private double direction;
private double speed;
private BufferedImage background;
public Car() {
setOpaque(false);
direction = Math.random() * 360;
speed = 5 + (Math.random() * 10);
int image = 1 + (int) Math.round(Math.random() * 5);
try {
String name = "/Car0" + image + ".png";
background = ImageIO.read(getClass().getResource(name));
} catch (IOException ex) {
ex.printStackTrace();
}
setSize(getPreferredSize());
// setBorder(new LineBorder(Color.RED));
}
public void setDirection(double direction) {
this.direction = direction;
revalidate();
repaint();
}
public double getDirection() {
return direction;
}
public void move() {
Point at = getLocation();
at.x += (int)(speed * Math.cos(Math.toRadians(-direction)));
at.y += (int)(speed * Math.sin(Math.toRadians(-direction)));
setLocation(at);
}
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (background != null) {
double radian = Math.toRadians(direction);
double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian));
int w = background.getWidth(), h = background.getHeight();
int neww = (int) Math.floor(w * cos + h * sin);
int newh = (int) Math.floor(h * cos + w * sin);
size = new Dimension(neww, newh);
}
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2);
g2d.drawImage(background, x, y, this);
g2d.dispose();
// Debug graphics...
// int cx = getWidth() / 2;
// int cy = getHeight() / 2;
//
// g2d = (Graphics2D) g.create();
// g2d.setColor(Color.BLUE);
// double radius = Math.min(getWidth(), getHeight());
// Point2D pointAt = getPointAt(radius, direction);
// g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius));
//
// double xo = cx;
// double yo = cy;
// double xPos = cx + pointAt.getX();
// double yPos = cy + pointAt.getY();
//
// g2d.draw(new Line2D.Double(xo, yo, xPos, yPos));
// g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4));
// g2d.dispose();
}
}
}
Updated with optimized version
I did a little bit of code optimisation with the creation of the car objects (there's still room for improvement) and ehanched the graphics ouput (made it look nicer).
Basically, now, when a car leaves the screen, it's placed in a pool. When another car is required, if possible, it's pulled from the pool, otherwise a new car is made. This has reduced the overhead of creating and destorying so many (relativly) short lived objects, which makes the memory usage a little more stable.
On my 2560x1600 resolution screen (running maximised), I was able to get 4500 cars running simultaneously. Once the object creation was reduced, it ran relatively smoothly (it's never going to run as well as 10, but it didn't suffer from a significant reduction in speed).
public class TestAnimation10 {
public static void main(String[] args) {
new TestAnimation10();
}
public TestAnimation10() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
final TrackPane trackPane = new TrackPane();
JSlider slider = new JSlider(1, 5000);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
trackPane.setCongestion(((JSlider) e.getSource()).getValue());
}
});
slider.setValue(5);
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(trackPane);
frame.add(slider, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TrackPane extends JPanel {
private List<Car> activeCarList;
private List<Car> carPool;
private int maxCars = 1;
private List<Point2D[]> points;
private Ellipse2D areaOfEffect;
public TrackPane() {
points = new ArrayList<>(25);
activeCarList = new ArrayList<>(25);
carPool = new ArrayList<>(25);
setLayout(null);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Rectangle bounds = areaOfEffect.getBounds();
List<Car> tmp = new ArrayList<>(activeCarList);
for (Car car : tmp) {
car.move();
if (!bounds.intersects(car.getBounds())) {
remove(car);
activeCarList.remove(car);
carPool.add(car);
}
}
updatePool();
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
updateAreaOfEffect();
}
protected void updateAreaOfEffect() {
double radius = Math.max(getWidth(), getHeight()) * 1.5d;
double x = (getWidth() - radius) / 2d;
double y = (getHeight() - radius) / 2d;
areaOfEffect = new Ellipse2D.Double(x, y, radius, radius);
}
#Override
public void invalidate() {
// super.invalidate();
updateAreaOfEffect();
}
protected void updatePool() {
if (activeCarList.size() < maxCars) {
int count = Math.min(maxCars - activeCarList.size(), 10);
for (int index = 0; index < count; index++) {
Car car = null;
if (carPool.isEmpty()) {
car = new Car();
} else {
car = carPool.remove(0);
}
double direction = car.getDirection();
double startAngle = direction - 180;
double radius = areaOfEffect.getWidth();
Point2D startPoint = getPointAt(radius, startAngle);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
double x = cx + (startPoint.getX() - car.getWidth() / 2);
double y = cy + (startPoint.getY() - car.getHeight() / 2);
car.setLocation((int) x, (int) y);
Point2D targetPoint = getPointAt(radius, direction);
points.add(new Point2D[]{startPoint, targetPoint});
add(car);
activeCarList.add(car);
}
}
}
#Override
public void paint(Graphics g) {
super.paint(g);
Font font = g.getFont();
font = font.deriveFont(Font.BOLD, 48f);
FontMetrics fm = g.getFontMetrics(font);
g.setFont(font);
g.setColor(Color.RED);
String text = Integer.toString(maxCars);
int x = getWidth() - fm.stringWidth(text);
int y = getHeight() - fm.getHeight() + fm.getAscent();
g.drawString(text, x, y);
text = Integer.toString(getComponentCount());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
text = Integer.toString(activeCarList.size());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
text = Integer.toString(carPool.size());
x = getWidth() - fm.stringWidth(text);
y -= fm.getHeight();
g.drawString(text, x, y);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
public void setCongestion(int value) {
maxCars = value;
}
#Override
public void validate() {
}
#Override
public void revalidate() {
}
// #Override
// public void repaint(long tm, int x, int y, int width, int height) {
// }
//
// #Override
// public void repaint(Rectangle r) {
// }
// public void repaint() {
// }
#Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
System.out.println(propertyName);
// // Strings get interned...
// if (propertyName == "text"
// || propertyName == "labelFor"
// || propertyName == "displayedMnemonic"
// || ((propertyName == "font" || propertyName == "foreground")
// && oldValue != newValue
// && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) {
//
// super.firePropertyChange(propertyName, oldValue, newValue);
// }
}
#Override
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
}
}
protected static Point2D getPointAt(double radius, double angle) {
double x = Math.round(radius / 2d);
double y = Math.round(radius / 2d);
double rads = Math.toRadians(-angle);
double fullLength = Math.round((radius / 2d));
double xPosy = (Math.cos(rads) * fullLength);
double yPosy = (Math.sin(rads) * fullLength);
return new Point2D.Double(xPosy, yPosy);
}
public class Car extends JPanel {
private double direction;
private double speed;
private BufferedImage background;
public Car() {
setOpaque(false);
direction = Math.random() * 360;
speed = 5 + (Math.random() * 10);
int image = 1 + (int) Math.round(Math.random() * 5);
try {
String name = "/Car0" + image + ".png";
background = ImageIO.read(getClass().getResource(name));
} catch (IOException ex) {
ex.printStackTrace();
}
setSize(getPreferredSize());
// setBorder(new LineBorder(Color.RED));
}
public void setDirection(double direction) {
this.direction = direction;
revalidate();
repaint();
}
public double getDirection() {
return direction;
}
public void move() {
Point at = getLocation();
at.x += (int) (speed * Math.cos(Math.toRadians(-direction)));
at.y += (int) (speed * Math.sin(Math.toRadians(-direction)));
setLocation(at);
}
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
if (background != null) {
double radian = Math.toRadians(direction);
double sin = Math.abs(Math.sin(radian)), cos = Math.abs(Math.cos(radian));
int w = background.getWidth(), h = background.getHeight();
int neww = (int) Math.floor(w * cos + h * sin);
int newh = (int) Math.floor(h * cos + w * sin);
size = new Dimension(neww, newh);
}
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
int x = (getWidth() - background.getWidth()) / 2;
int y = (getHeight() - background.getHeight()) / 2;
g2d.rotate(Math.toRadians(-(direction + 180)), getWidth() / 2, getHeight() / 2);
g2d.drawImage(background, x, y, this);
g2d.dispose();
// Debug graphics...
// int cx = getWidth() / 2;
// int cy = getHeight() / 2;
//
// g2d = (Graphics2D) g.create();
// g2d.setColor(Color.BLUE);
// double radius = Math.min(getWidth(), getHeight());
// Point2D pointAt = getPointAt(radius, direction);
// g2d.draw(new Ellipse2D.Double(cx - (radius / 2d), cy - (radius / 2d), radius, radius));
//
// double xo = cx;
// double yo = cy;
// double xPos = cx + pointAt.getX();
// double yPos = cy + pointAt.getY();
//
// g2d.draw(new Line2D.Double(xo, yo, xPos, yPos));
// g2d.draw(new Ellipse2D.Double(xPos - 2, yPos - 2, 4, 4));
// g2d.dispose();
}
#Override
public void invalidate() {
}
#Override
public void validate() {
}
#Override
public void revalidate() {
}
#Override
public void repaint(long tm, int x, int y, int width, int height) {
}
#Override
public void repaint(Rectangle r) {
}
#Override
public void repaint() {
}
#Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
// System.out.println(propertyName);
// // Strings get interned...
// if (propertyName == "text"
// || propertyName == "labelFor"
// || propertyName == "displayedMnemonic"
// || ((propertyName == "font" || propertyName == "foreground")
// && oldValue != newValue
// && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) {
//
// super.firePropertyChange(propertyName, oldValue, newValue);
// }
}
#Override
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
}
}
}
ps - I should add 1- My 10 month old loved it 2- It reminded me of the run to work :P
I'm trying to create a custom extension of BasicSliderUI. I'm just trying to make the thumb a circle (note I'm in the Windows L&F). I've created a very simple implementation that just calls g.drawOval, but whenever I drag it, it leaves a "trail" behind. Any ideas why this is?
thanks,
Jeff
You need to call repaint on the whole thing, you cant just draw the oval on top of it. Swing will by default only repaint what needs to be repainted, which usually isn't the whole control. When are you drawing the circle?
If you want to get rid of "trail" when you drag you should write your custom TrackListener and control trumb position related to mouse move.
Look at my implementation:
public class LightSliderUI extends BasicSliderUI{
private final Color rangeColor = Color.BLUE;
private final BasicStroke stroke = new BasicStroke(2f);
private transient boolean upperDragging;
public LightSliderUI(JSlider b) {
super(b);
}
public static ComponentUI createUI(JComponent c) {
return new LightSliderUI((JSlider)c);
}
#Override
protected void calculateThumbSize() {
super.calculateThumbSize();
thumbRect.setSize(thumbRect.width, thumbRect.height);
}
/** Creates a listener to handle track events in the specified slider.*/
#Override
protected TrackListener createTrackListener(JSlider slider) {
return new RangeTrackListener();
}
#Override
protected void calculateThumbLocation() {
// Call superclass method for lower thumb location.
super.calculateThumbLocation();
// Adjust upper value to snap to ticks if necessary.
if (slider.getSnapToTicks()) {
int upperValue = slider.getValue() + slider.getExtent();
int snappedValue = upperValue;
int majorTickSpacing = slider.getMajorTickSpacing();
int minorTickSpacing = slider.getMinorTickSpacing();
int tickSpacing = 0;
if (minorTickSpacing > 0) {
tickSpacing = minorTickSpacing;
} else if (majorTickSpacing > 0) {
tickSpacing = majorTickSpacing;
}
if (tickSpacing != 0) {
// If it's not on a tick, change the value
if ((upperValue - slider.getMinimum()) % tickSpacing != 0) {
float temp = (float)(upperValue - slider.getMinimum()) / (float)tickSpacing;
int whichTick = Math.round(temp);
snappedValue = slider.getMinimum() + (whichTick * tickSpacing);
}
if (snappedValue != upperValue) {
slider.setExtent(snappedValue - slider.getValue());
}
}
}
// Calculate upper thumb location. The thumb is centered over its
// value on the track.
if (slider.getOrientation() == JSlider.HORIZONTAL) {
int upperPosition = xPositionForValue(slider.getValue() + slider.getExtent());
thumbRect.x = upperPosition - (thumbRect.width / 2);
thumbRect.y = trackRect.y;
} else {
int upperPosition = yPositionForValue(slider.getValue() + slider.getExtent());
thumbRect.x = trackRect.x;
thumbRect.y = upperPosition - (thumbRect.height / 2);
}
slider.repaint();
}
/** Returns the size of a thumb.
* Parent method not use size from LaF
* #return size of trumb */
#Override
protected Dimension getThumbSize() {
return Dimensions.getSliderThumbSize();
}
private Shape createThumbShape(int width, int height) {
Ellipse2D shape = new Ellipse2D.Double(0, 0, width, height);
return shape;
}
#Override
public void paintTrack(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Stroke old = g2d.getStroke();
g2d.setStroke(stroke);
g2d.setPaint(Colors.TEXT_STEEL);
Color oldColor = Colors.TEXT_STEEL;
Rectangle trackBounds = trackRect;
if (slider.getOrientation() == SwingConstants.HORIZONTAL) {
g2d.drawLine(trackRect.x, trackRect.y + trackRect.height / 2,
trackRect.x + trackRect.width, trackRect.y + trackRect.height / 2);
int lowerX = thumbRect.width / 2;
int upperX = thumbRect.x + (thumbRect.width / 2);
int cy = (trackBounds.height / 2) - 2;
g2d.translate(trackBounds.x, trackBounds.y + cy);
g2d.setColor(rangeColor);
g2d.drawLine(lowerX - trackBounds.x, 2, upperX - trackBounds.x, 2);
g2d.translate(-trackBounds.x, -(trackBounds.y + cy));
g2d.setColor(oldColor);
}
g2d.setStroke(old);
}
/** Overrides superclass method to do nothing. Thumb painting is handled
* within the <code>paint()</code> method.*/
#Override
public void paintThumb(Graphics g) {
Rectangle knobBounds = thumbRect;
int w = knobBounds.width;
int h = knobBounds.height;
Graphics2D g2d = (Graphics2D) g.create();
Shape thumbShape = createThumbShape(w - 1, h - 1);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.translate(knobBounds.x, knobBounds.y);
g2d.setColor(Color.WHITE);
g2d.fill(thumbShape);
g2d.setColor(Colors.BIOLIN_BLUE_TINT);
g2d.draw(thumbShape);
g2d.dispose();
}
/** Listener to handle model change events. This calculates the thumb
* locations and repaints the slider if the value change is not caused by dragging a thumb.*/
public class ChangeHandler implements ChangeListener {
#Override
public void stateChanged(ChangeEvent arg0) {
calculateThumbLocation();
slider.repaint();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
JSlider slider = new JSlider(0, 100);
slider.setPaintTicks(true);
slider.setUI(new LightSliderUI(slider));
frame.add(slider);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
/** Listener to handle mouse movements in the slider track.*/
public class RangeTrackListener extends TrackListener {
#Override
public void mouseClicked(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
currentMouseX -= thumbRect.width / 2; // Because we want the mouse location correspond to middle of the "thumb", not left side of it.
moveUpperThumb();
}
public void mousePressed(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
currentMouseX = e.getX();
currentMouseY = e.getY();
if (slider.isRequestFocusEnabled()) {
slider.requestFocus();
}
boolean upperPressed = false;
if (slider.getMinimum() == slider.getValue()) {
if (thumbRect.contains(currentMouseX, currentMouseY)) {
upperPressed = true;
}
} else {
if (thumbRect.contains(currentMouseX, currentMouseY)) {
upperPressed = true;
}
}
if (upperPressed) {
switch (slider.getOrientation()) {
case JSlider.VERTICAL:
offset = currentMouseY - thumbRect.y;
break;
case JSlider.HORIZONTAL:
offset = currentMouseX - thumbRect.x;
break;
}
//upperThumbSelected = true;
upperDragging = true;
return;
}
upperDragging = false;
}
#Override
public void mouseReleased(MouseEvent e) {
upperDragging = false;
slider.setValueIsAdjusting(false);
super.mouseReleased(e);
}
#Override
public void mouseDragged(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
currentMouseX = e.getX();
currentMouseY = e.getY();
if (upperDragging) {
slider.setValueIsAdjusting(true);
moveUpperThumb();
}
}
#Override
public boolean shouldScroll(int direction) {
return false;
}
/** Moves the location of the upper thumb, and sets its corresponding value in the slider.*/
public void moveUpperThumb() {
int thumbMiddle = 0;
switch (slider.getOrientation()) {
case JSlider.HORIZONTAL:
int halfThumbWidth = thumbRect.width / 2;
int thumbLeft = currentMouseX - offset;
int trackLeft = trackRect.x;
int trackRight = trackRect.x + (trackRect.width - 1);
int hMax = xPositionForValue(slider.getMaximum() -
slider.getExtent());
if (drawInverted()) {
trackLeft = hMax;
}
else {
trackRight = hMax;
}
thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
setThumbLocation(thumbLeft, thumbRect.y);//setThumbLocation
thumbMiddle = thumbLeft + halfThumbWidth;
slider.setValue(valueForXPosition(thumbMiddle));
break;
default:
return;
}
}
}
}