How to create a component that can be dragged and resized in Java Swing?
Like "Text Tools" text box feature in MS Paint, highlighted by red border in image.
I only want drag and resize feature, not text formatting.
How can I implement this component using Java Swing?
In my quest to solve this problem, I found Piccolo2d ZUI library.
That's exactly, what I was looking for! However, it might not be best approach just to implement a "Draggable & Resizable Text Box", but I got it working in under an hour using Piccolo2d. So, I am posting the code here, if someone can find it useful.
Here is the screenshot of sample application!
I tried to make the code as describable as possible, so it's a bit long.
You'll require piccolo2d-core-XXX.jar & piccolo2d-extras-XXX.jar to run this code, that can be downloaded from Maven Central Repository. I used version 3.0 & didn't tested with any other version!
Custom Component Class
class PBox extends PNode {
private PCanvas canvas;
private Rectangle2D rectangle;
private Cursor moveCursor;
public PBox(PCanvas canvas) {
this(0, 0, 50, 50, canvas);
}
public PBox(double x, double y, double width, double height, PCanvas canvas) {
this.canvas = canvas;
rectangle = new Rectangle2D.Double();
moveCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
addAnchors(x, y, width, height);
setBounds(x, y, width, height);
setInputEventListener();
}
private void addAnchors(double x, double y, double width, double height) {
addChild(new Anchor(SwingConstants.NORTH));
addChild(new Anchor(SwingConstants.NORTH_EAST));
addChild(new Anchor(SwingConstants.EAST));
addChild(new Anchor(SwingConstants.SOUTH_EAST));
addChild(new Anchor(SwingConstants.SOUTH));
addChild(new Anchor(SwingConstants.SOUTH_WEST));
addChild(new Anchor(SwingConstants.WEST));
addChild(new Anchor(SwingConstants.NORTH_WEST));
}
private void setInputEventListener() {
addInputEventListener(new PBasicInputEventHandler() {
#Override
public void mouseEntered(PInputEvent event) {
canvas.setCursor(moveCursor);
}
#Override
public void mouseExited(PInputEvent event) {
canvas.setCursor(Cursor.getDefaultCursor());
}
#Override
public void mouseDragged(PInputEvent event) {
PDimension delta = event.getDeltaRelativeTo(PBox.this);
translate(delta.width, delta.height);
event.setHandled(true);
}
});
}
#Override
protected void layoutChildren() {
Iterator iterator = getChildrenIterator();
int position = SwingConstants.NORTH;
while (iterator.hasNext()) {
PNode anchor = (PNode) iterator.next();
anchor.setBounds(getAnchorBounds(position));
++position;
}
}
private Rectangle2D getAnchorBounds(int position) {
double x = 0, y = 0;
Rectangle2D b = getBounds();
switch (position) {
case SwingConstants.NORTH:
x = b.getX()+b.getWidth()/2;
y = b.getY();
break;
case SwingConstants.NORTH_EAST:
x = b.getX()+b.getWidth();
y = b.getY();
break;
case SwingConstants.EAST:
x = b.getX()+b.getWidth();
y = b.getY()+b.getHeight()/2;
break;
case SwingConstants.SOUTH_EAST:
x = b.getX()+b.getWidth();
y = b.getY()+b.getHeight();
break;
case SwingConstants.SOUTH:
x = b.getX()+b.getWidth()/2;
y = b.getY()+b.getHeight();
break;
case SwingConstants.SOUTH_WEST:
x = b.getX();
y = b.getY()+b.getHeight();
break;
case SwingConstants.WEST:
x = b.getX();
y = b.getY()+b.getHeight()/2;
break;
case SwingConstants.NORTH_WEST:
x = b.getX();
y = b.getY();
break;
}
return new Rectangle2D.Double(x-2, y-2, 4, 4);
}
#Override
public boolean setBounds(double x, double y, double width, double height) {
if (super.setBounds(x, y, width, height)) {
rectangle.setFrame(x, y, width, height);
return true;
}
return false;
}
#Override
public boolean intersects(Rectangle2D localBounds) {
return rectangle.intersects(localBounds);
}
#Override
protected void paint(PPaintContext paintContext) {
Graphics2D g2 = paintContext.getGraphics();
g2.setPaint(Color.BLACK);
g2.setStroke(new BasicStroke(1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
1.0f, new float[]{4.0f}, 0));
g2.draw(rectangle);
}
class Anchor extends PNode {
private Rectangle2D point;
private Cursor resizeCursor;
Anchor(int position) {
point = new Rectangle2D.Double();
setCursor(position);
setInputEventListener(position);
}
private void setCursor(int position) {
switch (position) {
case SwingConstants.NORTH:
resizeCursor = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);
break;
case SwingConstants.NORTH_EAST:
resizeCursor = Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR);
break;
case SwingConstants.EAST:
resizeCursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
break;
case SwingConstants.SOUTH_EAST:
resizeCursor = Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR);
break;
case SwingConstants.SOUTH:
resizeCursor = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);
break;
case SwingConstants.SOUTH_WEST:
resizeCursor = Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR);
break;
case SwingConstants.WEST:
resizeCursor = Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR);
break;
case SwingConstants.NORTH_WEST:
resizeCursor = Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR);
break;
default:
resizeCursor = Cursor.getDefaultCursor();
}
}
private void setInputEventListener(final int position) {
addInputEventListener(new PBasicInputEventHandler() {
#Override
public void mouseEntered(PInputEvent event) {
canvas.setCursor(resizeCursor);
event.setHandled(true);
}
#Override
public void mouseExited(PInputEvent event) {
canvas.setCursor(Cursor.getDefaultCursor());
event.setHandled(true);
}
#Override
public void mouseDragged(PInputEvent event) {
PDimension delta = event.getDeltaRelativeTo(Anchor.this);
PNode parent = getParent();
if (position == SwingConstants.EAST
|| position == SwingConstants.NORTH_EAST
|| position == SwingConstants.SOUTH_EAST) {
parent.setWidth(parent.getWidth() + delta.width);
} else if (position == SwingConstants.WEST
|| position == SwingConstants.NORTH_WEST
|| position == SwingConstants.SOUTH_WEST) {
parent.setX(parent.getX() + delta.width);
parent.setWidth(parent.getWidth() - delta.width);
}
if (position == SwingConstants.SOUTH
|| position == SwingConstants.SOUTH_EAST
|| position == SwingConstants.SOUTH_WEST) {
parent.setHeight(parent.getHeight() + delta.height);
} else if (position == SwingConstants.NORTH
|| position == SwingConstants.NORTH_EAST
|| position == SwingConstants.NORTH_WEST) {
parent.setY(parent.getY() + delta.height);
parent.setHeight(parent.getHeight() - delta.height);
}
event.setHandled(true);
}
});
}
#Override
public boolean setBounds(double x, double y, double width, double height) {
if (super.setBounds(x, y, width, height)) {
point.setFrame(x, y, width, height);
return true;
}
return false;
}
#Override
public boolean intersects(Rectangle2D localBounds) {
return point.intersects(localBounds);
}
#Override
protected void paint(PPaintContext paintContext) {
Graphics2D g2 = paintContext.getGraphics();
g2.setColor(Color.WHITE);
g2.fill(point);
g2.setStroke(new BasicStroke(1.0f));
g2.setColor(Color.BLACK);
g2.draw(point);
}
}
}
And, the main class
public class Piccolo2DExample extends PFrame {
#Override
public void initialize() {
PCanvas canvas = getCanvas();
PNode text = new PText("Draggable & Resizable Box using Piccolo2d");
text.scale(2.0);
canvas.getCamera().addChild(text);
PLayer layer = canvas.getLayer();
PNode aLayerNode = new PText("A_Layer_Node");
aLayerNode.setOffset(10, 50);
layer.addChild(aLayerNode);
// Adding the component to layer
layer.addChild(new PBox(50, 100, 250, 50, canvas));
}
public static void main(String[] args) {
PFrame frame = new Piccolo2DExample();
frame.setSize(800, 640);
}
}
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.????(????);
}
I am trying to implement a Drawing Application in Android where the user should be able to select and move the drawn shapes.
Currently, I have statically drawn some rects and text on my Drawing Canvas.
I want to select (draw borders on the selected shape) and move the drawn shapes in onTouch events of the drawing canvas.
package com.android.graphics;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
public class CanvasView extends View {
public enum Mode {
DRAW,
TEXT,
ERASER;
}
public enum Drawer {
PEN,
LINE,
RECTANGLE,
CIRCLE,
ELLIPSE,
QUADRATIC_BEZIER,
QUBIC_BEZIER;
}
private Canvas canvas = null;
private Bitmap bitmap = null;
private List<Path> pathLists = new ArrayList<Path>();
private List<Paint> paintLists = new ArrayList<Paint>();
private final Paint emptyPaint = new Paint();
private int baseColor = Color.WHITE;
// for Undo, Redo
private int historyPointer = 0;
// Flags
private Mode mode = Mode.DRAW;
private Drawer drawer = Drawer.PEN;
private boolean isDown = false;
// for Paint
private Paint.Style paintStyle = Paint.Style.STROKE;
private int paintStrokeColor = Color.BLACK;
private int paintFillColor = Color.BLACK;
private float paintStrokeWidth = 3F;
private int opacity = 255;
private float blur = 0F;
private Paint.Cap lineCap = Paint.Cap.ROUND;
private PathEffect drawPathEffect = null;
// for Text
private String text = "";
private Typeface fontFamily = Typeface.DEFAULT;
private float fontSize = 32F;
private Paint.Align textAlign = Paint.Align.RIGHT; // fixed
private Paint textPaint = new Paint();
private float textX = 0F;
private float textY = 0F;
// for Drawer
private float startX = 0F;
private float startY = 0F;
private float controlX = 0F;
private float controlY = 0F;
public CanvasView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setup();
}
public CanvasView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setup();
}
public CanvasView(Context context) {
super(context);
this.setup();
}
private void setup() {
this.pathLists.add(new Path());
this.paintLists.add(this.createPaint());
this.historyPointer++;
this.textPaint.setARGB(0, 255, 255, 255);
}
private Paint createPaint() {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(this.paintStyle);
paint.setStrokeWidth(this.paintStrokeWidth);
paint.setStrokeCap(this.lineCap);
paint.setStrokeJoin(Paint.Join.MITER); // fixed
// for Text
if (this.mode == Mode.TEXT) {
paint.setTypeface(this.fontFamily);
paint.setTextSize(this.fontSize);
paint.setTextAlign(this.textAlign);
paint.setStrokeWidth(0F);
}
if (this.mode == Mode.ERASER) {
// Eraser
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setARGB(0, 0, 0, 0);
} else {
// Otherwise
paint.setColor(this.paintStrokeColor);
paint.setShadowLayer(this.blur, 0F, 0F, this.paintStrokeColor);
paint.setAlpha(this.opacity);
paint.setPathEffect(this.drawPathEffect);
}
return paint;
}
private Path createPath(MotionEvent event) {
Path path = new Path();
// Save for ACTION_MOVE
this.startX = event.getX();
this.startY = event.getY();
path.moveTo(this.startX, this.startY);
return path;
}
private void updateHistory(Path path) {
if (this.historyPointer == this.pathLists.size()) {
this.pathLists.add(path);
this.paintLists.add(this.createPaint());
this.historyPointer++;
} else {
// On the way of Undo or Redo
this.pathLists.set(this.historyPointer, path);
this.paintLists.set(this.historyPointer, this.createPaint());
this.historyPointer++;
for (int i = this.historyPointer, size = this.paintLists.size(); i < size; i++) {
this.pathLists.remove(this.historyPointer);
this.paintLists.remove(this.historyPointer);
}
}
}
private Path getCurrentPath() {
return this.pathLists.get(this.historyPointer - 1);
}
private void drawText(Canvas canvas) {
if (this.text.length() <= 0) {
return;
}
if (this.mode == Mode.TEXT) {
this.textX = this.startX;
this.textY = this.startY;
this.textPaint = this.createPaint();
}
float textX = this.textX;
float textY = this.textY;
Paint paintForMeasureText = new Paint();
// Line break automatically
float textLength = paintForMeasureText.measureText(this.text);
float lengthOfChar = textLength / (float) this.text.length();
float restWidth = this.canvas.getWidth() - textX; // text-align : right
int numChars = (lengthOfChar <= 0) ? 1 : (int) Math.floor((double) (restWidth / lengthOfChar)); // The number of characters at 1 line
int modNumChars = (numChars < 1) ? 1 : numChars;
float y = textY;
for (int i = 0, len = this.text.length(); i < len; i += modNumChars) {
String substring = "";
if ((i + modNumChars) < len) {
substring = this.text.substring(i, (i + modNumChars));
} else {
substring = this.text.substring(i, len);
}
y += this.fontSize;
canvas.drawText(substring, textX, y, this.textPaint);
}
}
private void onActionDown(MotionEvent event) {
switch (this.mode) {
case DRAW:
case ERASER:
if ((this.drawer != Drawer.QUADRATIC_BEZIER) && (this.drawer != Drawer.QUBIC_BEZIER)) {
// Oherwise
this.updateHistory(this.createPath(event));
this.isDown = true;
} else {
// Bezier
if ((this.startX == 0F) && (this.startY == 0F)) {
// The 1st tap
this.updateHistory(this.createPath(event));
} else {
// The 2nd tap
this.controlX = event.getX();
this.controlY = event.getY();
this.isDown = true;
}
}
break;
case TEXT:
this.startX = event.getX();
this.startY = event.getY();
break;
default:
break;
}
}
private void onActionMove(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (this.mode) {
case DRAW:
case ERASER:
if ((this.drawer != Drawer.QUADRATIC_BEZIER) && (this.drawer != Drawer.QUBIC_BEZIER)) {
if (!isDown) {
return;
}
Path path = this.getCurrentPath();
switch (this.drawer) {
case PEN:
path.lineTo(x, y);
break;
case LINE:
path.reset();
path.moveTo(this.startX, this.startY);
path.lineTo(x, y);
break;
case RECTANGLE:
path.reset();
float left = Math.min(this.startX, x);
float right = Math.max(this.startX, x);
float top = Math.min(this.startY, y);
float bottom = Math.max(this.startY, y);
path.addRect(left, top, right, bottom, Path.Direction.CCW);
break;
case CIRCLE:
double distanceX = Math.abs((double) (this.startX - x));
double distanceY = Math.abs((double) (this.startX - y));
double radius = Math.sqrt(Math.pow(distanceX, 2.0) + Math.pow(distanceY, 2.0));
path.reset();
path.addCircle(this.startX, this.startY, (float) radius, Path.Direction.CCW);
break;
case ELLIPSE:
RectF rect = new RectF(this.startX, this.startY, x, y);
path.reset();
path.addOval(rect, Path.Direction.CCW);
break;
default:
break;
}
} else {
if (!isDown) {
return;
}
Path path = this.getCurrentPath();
path.reset();
path.moveTo(this.startX, this.startY);
path.quadTo(this.controlX, this.controlY, x, y);
}
break;
case TEXT:
this.startX = x;
this.startY = y;
break;
default:
break;
}
}
private void onActionUp(MotionEvent event) {
if (isDown) {
this.startX = 0F;
this.startY = 0F;
this.isDown = false;
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Before "drawPath"
canvas.drawColor(this.baseColor);
if (this.bitmap != null) {
canvas.drawBitmap(this.bitmap, 0F, 0F, emptyPaint);
}
for (int i = 0; i < this.historyPointer; i++) {
Path path = this.pathLists.get(i);
Paint paint = this.paintLists.get(i);
canvas.drawPath(path, paint);
}
this.drawText(canvas);
this.canvas = canvas;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
this.onActionDown(event);
break;
case MotionEvent.ACTION_MOVE:
this.onActionMove(event);
break;
case MotionEvent.ACTION_UP:
this.onActionUp(event);
break;
default:
break;
}
// Re draw
this.invalidate();
return true;
}
public Mode getMode() {
return this.mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public Drawer getDrawer() {
return this.drawer;
}
public void setDrawer(Drawer drawer) {
this.drawer = drawer;
}
public boolean canUndo() {
return this.historyPointer > 1;
}
public boolean canRedo() {
return this.historyPointer < this.pathLists.size();
}
public boolean undo() {
if (canUndo()) {
this.historyPointer--;
this.invalidate();
return true;
} else {
return false;
}
}
public boolean redo() {
if (canRedo()) {
this.historyPointer++;
this.invalidate();
return true;
} else {
return false;
}
}
public void clear() {
Path path = new Path();
path.moveTo(0F, 0F);
path.addRect(0F, 0F, 3000F, 3000F, Path.Direction.CCW);
path.close();
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
if (this.historyPointer == this.pathLists.size()) {
this.pathLists.add(path);
this.paintLists.add(paint);
this.historyPointer++;
} else {
// On the way of Undo or Redo
this.pathLists.set(this.historyPointer, path);
this.paintLists.set(this.historyPointer, paint);
this.historyPointer++;
for (int i = this.historyPointer, size = this.paintLists.size(); i < size; i++) {
this.pathLists.remove(this.historyPointer);
this.paintLists.remove(this.historyPointer);
}
}
this.text = "";
// Clear
this.invalidate();
}
public int getBaseColor() {
return this.baseColor;
}
public void setBaseColor(int color) {
this.baseColor = color;
}
public String getText() {
return this.text;
}
public void setText(String text) {
this.text = text;
}
public Paint.Style getPaintStyle() {
return this.paintStyle;
}
public void setPaintStyle(Paint.Style style) {
this.paintStyle = style;
}
public int getPaintStrokeColor() {
return this.paintStrokeColor;
}
public void setPaintStrokeColor(int color) {
this.paintStrokeColor = color;
}
public int getPaintFillColor() {
return this.paintFillColor;
}
public void setPaintFillColor(int color) {
this.paintFillColor = color;
}
public float getPaintStrokeWidth() {
return this.paintStrokeWidth;
}
public void setPaintStrokeWidth(float width) {
if (width >= 0) {
this.paintStrokeWidth = width;
} else {
this.paintStrokeWidth = 3F;
}
}
public int getOpacity() {
return this.opacity;
}
public void setOpacity(int opacity) {
if ((opacity >= 0) && (opacity <= 255)) {
this.opacity = opacity;
} else {
this.opacity = 255;
}
}
public float getBlur() {
return this.blur;
}
public void setBlur(float blur) {
if (blur >= 0) {
this.blur = blur;
} else {
this.blur = 0F;
}
}
public Paint.Cap getLineCap() {
return this.lineCap;
}
public void setLineCap(Paint.Cap cap) {
this.lineCap = cap;
}
public PathEffect getDrawPathEffect() {
return drawPathEffect;
}
public void setDrawPathEffect(PathEffect drawPathEffect) {
this.drawPathEffect = drawPathEffect;
}
public float getFontSize() {
return this.fontSize;
}
public void setFontSize(float size) {
if (size >= 0F) {
this.fontSize = size;
} else {
this.fontSize = 32F;
}
}
public Typeface getFontFamily() {
return this.fontFamily;
}
public void setFontFamily(Typeface face) {
this.fontFamily = face;
}
public Bitmap getBitmap() {
this.setDrawingCacheEnabled(false);
this.setDrawingCacheEnabled(true);
return Bitmap.createBitmap(this.getDrawingCache());
}
public Bitmap getScaleBitmap(int w, int h) {
this.setDrawingCacheEnabled(false);
this.setDrawingCacheEnabled(true);
return Bitmap.createScaledBitmap(this.getDrawingCache(), w, h, true);
}
public void drawBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
this.invalidate();
}
public void drawBitmap(byte[] byteArray) {
this.drawBitmap(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length));
}
public static byte[] getBitmapAsByteArray(Bitmap bitmap, CompressFormat format, int quality) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(format, quality, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
public byte[] getBitmapAsByteArray(CompressFormat format, int quality) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
this.getBitmap().compress(format, quality, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
public byte[] getBitmapAsByteArray() {
return this.getBitmapAsByteArray(CompressFormat.PNG, 100);
}
}
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 made a program to display interference patterns for light waves. I did this by using the paint method on a JPanel to draw 2 sources and then drawing concentric circles around them. This would be double slit interference, so I allowed one of the sources to move around to experiment with the slit width.
The problem is that when I run this, my computer says it is using 80% of my CPU! There's really not much to it. Circle in the middle, circles around it, and it moves. My code follows.
main class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class main {
static final int UP = -1;
static final int DOWN = 1;
static final int RIGHT = 1;
static final int LEFT = -1;
static final int NOMOVEMENT = 0;
static int verticalMovement = NOMOVEMENT;
static int horizontalMovement = NOMOVEMENT;
public static void main(String[] args) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Point s1 = new Point((int)(screenSize.getWidth()/3), 50);
Point s2 = new Point((int)(2*screenSize.getWidth()/3), 50);
JFrame frame = new JFrame("Physics Frame");
frame.setPreferredSize(screenSize);
PhysicsPane pane = new PhysicsPane(screenSize, 15, s1, s2);
pane.setPreferredSize(screenSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
Timer time = new Timer(1000 / 20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (verticalMovement){
case UP:
s2.changeY(UP);
break;
case DOWN:
s2.changeY(DOWN);
break;
default:
break;
}
switch (horizontalMovement){
case RIGHT:
s2.changeX(RIGHT);
break;
case LEFT:
s2.changeX(LEFT);
break;
default:
break;
}
pane.repaint();
}
});
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==37){
horizontalMovement = LEFT;
} else if (e.getKeyCode()==38){
verticalMovement = UP;
} else if (e.getKeyCode()==39){
horizontalMovement = RIGHT;
} else if (e.getKeyCode()==40){
verticalMovement = DOWN;
}
if(e.getKeyChar()=='a'){
pane.setWaveLength(2);
}
if(e.getKeyChar()=='s'){
pane.setWaveLength(-2);
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()){
case 37:
horizontalMovement = NOMOVEMENT;
break;
case 38:
verticalMovement = NOMOVEMENT;
break;
case 39:
horizontalMovement = NOMOVEMENT;
break;
case 40:
verticalMovement = NOMOVEMENT;
break;
}
}
});
frame.setVisible(true);
time.start();
}
}
Panel class. If there's an inefficiency with the drawing method, it would be here.
import javax.swing.*;
import java.awt.*;
public class PhysicsPane extends JPanel {
private Dimension size;
private Point[] pointArray = new Point[2];
private double waveLength;
public PhysicsPane(Dimension size, double wavelength, Point source1, Point source2) {
this.size = size;
pointArray[0] = source1;
pointArray[1] = source2;
setPreferredSize(size);
this.waveLength = wavelength;
}
#Override
public void paintComponent(Graphics g){
for (int i = 0; i < 2; i++) {
g.setColor(Color.black);
double x = this.pointArray[i].getX();
double y = this.pointArray[i].getY();
g.fillOval((int)x, (int)y, 2, 2);
for (int j = (int)waveLength; j < 1500; j+=waveLength) {
g.setColor(Color.red);
g.drawOval((int)x-(j/2+1), (int)y-(j/2+1), 2 + j, 2 + j);
}
}
}
public void setWaveLength(double increment){
this.waveLength+=increment;
}
}
Point Class: Really just puts x and y coordinates into one construct. Not important particularly
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public void changeX(double dX){
this.x+=dX;
}
public void changeY(double dY){
this.y+=dY;
}
}
So what is wrong with my program? Why is such a simple animation consuming so much of my processor?
UPDATED CODE SECTION:
#Override
public void paintComponent(Graphics g){
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = bi.getGraphics();
for (int i = 0; i < 2; i++) {
graphics.setColor(Color.black);
double x = this.pointArray[i].getX();
double y = this.pointArray[i].getY();
graphics.fillOval((int)x, (int)y, 2, 2);
for (int j = (int)waveLength; j < 1500; j+=waveLength) {
graphics.setColor(Color.red);
graphics.drawOval((int)x-(j/2+1), (int)y-(j/2+1), 2 + j, 2 + j);
}
}
g.drawImage(bi, 0, 0, new ImageObserver() {
#Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
return false;
}
});
}
The improvement that I recommend is removal of unnecessary tasks being performed, notably how your code updates the pane being drawn on, even when there aren't changes.
The following update reduced CPU usage from 12% to 0% (static frame):
Timer time = new Timer(1000 / 20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
boolean refresh = false;
switch (verticalMovement) {
case UP:
s2.changeY(UP);
refresh = true;
break;
case DOWN:
s2.changeY(DOWN);
refresh = true;
break;
default:
break;
}
switch (horizontalMovement) {
case RIGHT:
s2.changeX(RIGHT);
refresh = true;
break;
case LEFT:
s2.changeX(LEFT);
refresh = true;
break;
default:
break;
}
if (refresh == true) {
pane.repaint();
}
}
});
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;
}
}
}
}