While trying to port swing code to be compliant with the Java module system I got stuck trying to replace SwingUtilities3.setDelegateRepaintManager.
I have a component, which when any of its children requests a repaint then I need to transform the region (in particular this is trying to port the code of org.pbjar.jxlayer.plaf.ext.TransformUI for anyone who knows of this). Currently this is done by setting the delegate repaint manager of the component and intercepting the calls to addDirtyRegion.
Now with Java 9 the method to do so isn't available anymore as public api. The original code provided an alternative method which was originally used for older versions of Java where SwingUtilities3.setDelegateRepaintManagerwasn't available, which simply replaced the global RepaintManager with a delegating implementation. It checks each call if the component is contained in the actual component which needs the transformation. This solution however throws away all internal data of the RepaintManager and results in heavy flickering while the frame is resized.
Here an abridged version of the code currently used:
SwingUtilities3.setDelegateRepaintManager(component, new TransformRepaintManger());
...
class TransformRepaintManager extends RepaintManager {
#Override
public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
if (c.isShowing()) {
Point point = c.getLocationOnScreen();
SwingUtilities.convertPointFromScreen(point, c);
Rectangle transformPortRegion = transform(new Rectangle(x + point.x, y + point.y, w, h), c);
RepaintManager.currentManager(c)
.addDirtyRegion(c,
transformPortRegion.x, transformPortRegion.y,
transformPortRegion.width, transformPortRegion.height);
}
}
}
and the alternative approach which causes flickering (DelegateRepaintManager simply takes the original RepaintManager and forwards all calls to it):
class TransformRPMFallBack extends DelegateRepaintManager {
#Override
public void addDirtyRegion(JComponent aComponent,int x, int y, int w, int h) {
if (aComponent.isShowing()) {
JComponent targetParent = findTargetParent(aComponent);
if (targetParent != null) {
Point point = aComponent.getLocationOnScreen();
SwingUtilities.convertPointFromScreen(point, targetParent);
Rectangle transformPortRegion = transform(new Rectangle(x + point.x, y + point.y, w, h));
addDirtyRegion(targetParent,
transformPortRegion.x, transformPortRegion.y,
transformPortRegion.width, transformPortRegion.height);
return;
}
}
super.addDirtyRegion(aComponent, x, y, w, h);
}
}
I know one option would be to simply add --add-exports java.desktop/com.sun.java.swing=<module name> to the startup parameters, but as this is intended for a library forcing everyone using it to do so isn't the best option in my opinion.
Update:
Here is an example which demonstrates the two approaches above.
It consists of a panel which is rotated 90 degree in the JLayer. Toggling the checkboxes paints the left (visually top) or right (visually bottom) part of the component in a different color. The different approaches can be changed by setting the static vairable in TransformLayerUI. One can observe the following behaviours:
SolutionApproach.FLICKERING: Clicking the checkboxes behaves as expected. Resizing the window results in flickering (for such a small example it isn't very bas but for larger applications it gets much worse)
SolutionApproach.ILLEGAL: Same as SolutionApproach.FLICKERING but without the flickering.
SolutionApproach.NONE: Clicking the checkboxes only repaints a quarter of the area that should change. This is the problem that needs to be solved. If the TestPanel (or any possible children) requests a repaint the correct area of the JLayer should be repainted.
public class TransformTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Transform Test");
JPanel contentPanel = new JPanel(new BorderLayout());
TestPanel testPanel = new TestPanel();
JLayer<TestPanel> testLayer = new JLayer<>(testPanel, new TransformLayerUI());
contentPanel.add(testLayer);
frame.getContentPane().add(contentPanel, BorderLayout.CENTER);
JCheckBox leftCheck = new JCheckBox("Left active");
leftCheck.addActionListener(e -> testPanel.setLeftActive(leftCheck.isSelected()));
JCheckBox rightCheck = new JCheckBox("Right active");
rightCheck.addActionListener(e -> testPanel.setRightActive(rightCheck.isSelected()));
JComponent buttonPanel = Box.createHorizontalBox();
buttonPanel.add(leftCheck);
buttonPanel.add(rightCheck);
frame.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
#SuppressWarnings("unchecked")
static class TransformLayerUI extends LayerUI<TestPanel> {
enum SolutionApproach {
ILLEGAL,
FLICKERING,
NONE
}
static SolutionApproach approach = SolutionApproach.ILLEGAL;
#Override
public void installUI(JComponent c) {
super.installUI(c);
switch (approach) {
case ILLEGAL:
SwingUtilities3.setDelegateRepaintManager(((JLayer<? extends JComponent>) c).getView(),
new TransformRepaintManager());
break;
case FLICKERING:
if (!(RepaintManager.currentManager(c) instanceof FallbackTransformRepaintManger)) {
RepaintManager.setCurrentManager(
new FallbackTransformRepaintManger(RepaintManager.currentManager(c)));
}
break;
case NONE:
break;
}
}
private AffineTransform calcTransform(Dimension size) {
AffineTransform at = new AffineTransform();
Point2D center = new Point2D.Double(size.getWidth() / 2f, size.getHeight() / 2f);
at.translate(center.getX(), center.getY());
at.quadrantRotate(1);
at.translate(-center.getX(), -center.getY());
return at;
}
private Rectangle transform(Dimension size, final Rectangle rect) {
Area area = new Area(rect);
area.transform(calcTransform(size));
return area.getBounds();
}
#Override
public void paint(Graphics g, JComponent c) {
((Graphics2D) g).transform(calcTransform(c.getSize()));
super.paint(g, c);
}
#Override
public void doLayout(JLayer<? extends TestPanel> l) {
l.getView().setBounds(transform(l.getSize(), new Rectangle(l.getSize())));
}
#Override
public Dimension getPreferredSize(JComponent c) {
return transform(((JLayer<? extends JComponent>) c).getView().getPreferredSize());
}
#Override
public Dimension getMaximumSize(JComponent c) {
return transform(((JLayer<? extends JComponent>) c).getView().getMaximumSize());
}
#Override
public Dimension getMinimumSize(JComponent c) {
return transform(((JLayer<? extends JComponent>) c).getView().getMinimumSize());
}
private Dimension transform(final Dimension size) {
Area area = new Area(new Rectangle2D.Double(0, 0, size.getWidth(), size.getHeight()));
area.transform(calcTransform(size));
Rectangle2D bounds = area.getBounds2D();
size.setSize(bounds.getWidth(), bounds.getHeight());
return size;
}
class TransformRepaintManager extends RepaintManager {
#Override
public void addInvalidComponent(JComponent invalidComponent) {
Container layer = SwingUtilities.getAncestorOfClass(JLayer.class, invalidComponent);
RepaintManager.currentManager(layer).addInvalidComponent((JComponent) layer);
}
#Override
public void addDirtyRegion(JComponent comp, int x, int y, int w, int h) {
if (comp.isShowing()) {
Container layer = SwingUtilities.getAncestorOfClass(JLayer.class, comp);
dispatchRepaint(comp, layer, TransformLayerUI.this, new Rectangle(x, y, w, h));
}
}
}
static void dispatchRepaint(Component comp, Component layer, TransformLayerUI ui, Rectangle rect) {
Point point = comp.getLocationOnScreen();
SwingUtilities.convertPointFromScreen(point, layer);
Rectangle transformPortRegion =
ui.transform(layer.getSize(),
new Rectangle(rect.x + point.x, rect.y + point.y, rect.width, rect.height));
RepaintManager.currentManager(layer).addDirtyRegion((JComponent) layer,
transformPortRegion.x, transformPortRegion.y,
transformPortRegion.width, transformPortRegion.height);
}
static class FallbackTransformRepaintManger extends DelegateRepaintManager {
FallbackTransformRepaintManger(RepaintManager delegate) {
super(delegate);
}
#Override
public void addDirtyRegion(JComponent aComponent, int x, int y, int w, int h) {
if (aComponent.isShowing()) {
JLayer<?> layer = (JLayer<?>) SwingUtilities.getAncestorOfClass(JLayer.class, aComponent);
if (layer != null) {
LayerUI<?> layerUI = layer.getUI();
if (layerUI instanceof TransformLayerUI) {
TransformLayerUI ui = (TransformLayerUI) layerUI;
dispatchRepaint(aComponent, layer, ui, new Rectangle(x, y, w, h));
return;
}
}
}
super.addDirtyRegion(aComponent, x, y, w, h);
}
}
static class DelegateRepaintManager extends RepaintManager {
private final RepaintManager delegate;
DelegateRepaintManager(RepaintManager delegate) {
this.delegate = delegate;
}
#Override
public void addInvalidComponent(JComponent invalidComponent) {
delegate.addInvalidComponent(invalidComponent);
}
#Override
public void removeInvalidComponent(JComponent component) {
delegate.removeInvalidComponent(component);
}
#Override
public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
delegate.addDirtyRegion(c, x, y, w, h);
}
#Override
public void addDirtyRegion(Window window, int x, int y, int w, int h) {
delegate.addDirtyRegion(window, x, y, w, h);
}
#Override
#Deprecated
public void addDirtyRegion(Applet applet, int x, int y, int w, int h) {
delegate.addDirtyRegion(applet, x, y, w, h);
}
#Override
public Rectangle getDirtyRegion(final JComponent c) {
return delegate.getDirtyRegion(c);
}
#Override
public void markCompletelyDirty(final JComponent c) {
delegate.markCompletelyDirty(c);
}
#Override
public boolean isCompletelyDirty(final JComponent c) {
return delegate.isCompletelyDirty(c);
}
#Override
public Dimension getDoubleBufferMaximumSize() {
return delegate.getDoubleBufferMaximumSize();
}
#Override
public void markCompletelyClean(final JComponent c) {
delegate.markCompletelyClean(c);
}
public RepaintManager getDelegateManager() {
return delegate;
}
#Override
public void setDoubleBufferMaximumSize(final Dimension d) {
delegate.setDoubleBufferMaximumSize(d);
}
#Override
public void validateInvalidComponents() {
delegate.validateInvalidComponents();
}
#Override
public void paintDirtyRegions() {
delegate.paintDirtyRegions();
}
#Override
public Image getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
return delegate.getOffscreenBuffer(c, proposedWidth, proposedHeight);
}
#Override
public Image getVolatileOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
return delegate.getVolatileOffscreenBuffer(c, proposedWidth, proposedHeight);
}
#Override
public boolean isDoubleBufferingEnabled() {
return delegate.isDoubleBufferingEnabled();
}
#Override
public void setDoubleBufferingEnabled(final boolean flag) {
delegate.setDoubleBufferingEnabled(flag);
}
}
}
static class TestPanel extends JPanel {
private boolean leftActive;
private boolean rightActive;
TestPanel() {
setPreferredSize(new Dimension(600, 300));
setMinimumSize(new Dimension(600, 300));
setMaximumSize(new Dimension(600, 300));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (leftActive) {
g.setColor(Color.RED);
g.fillRect(0, 0, getWidth() / 2, getHeight());
}
if (rightActive) {
g.setColor(Color.GREEN);
g.fillRect(getWidth() / 2, 0, getWidth() / 2, getHeight());
}
g.setColor(Color.BLACK);
g.drawString("Left", (getWidth() - 50) / 4, getHeight() / 2 + 10);
g.drawString("Right", getWidth() / 2 + (getWidth() - 50) / 4, getHeight() / 2 + 10);
}
public void setLeftActive(boolean leftActive) {
this.leftActive = leftActive;
repaint(0, 0, getWidth() / 2, getHeight());
}
public void setRightActive(boolean rightActive) {
this.rightActive = rightActive;
repaint(getWidth() / 2, 0, getWidth() / 2, getHeight());
}
}
}
Update 2:
Having FallbackTransformRepaintManger extend RepaintManager instead of DelegateRepaintManager. However this would make using the component a destructive operation as any other custom RepaintManager set previously would be overwritten.
Related
I am in the process of implementing a simple graph editor. It should be possible to create any Node (Sphere object) by mouse click and move it by drag and move. It works as far as it goes, but it behaves very strangely. When I click on the node, it slides away from the cursor. The hitbox from every Sphere object is strange too, but I can't explain why. Is there a better solution for my code?
This behavior
public class EditorPanelView extends Panel
{
private static EditorPanelView instance = null;
private static GraphController graphController = GraphController.getInstance();
private EditorPanelView(Color color, int x, int y, int width, int height) {
super(new BorderLayout(), color, x, y, width, height);
addMouseAdapter();
}
private void addMouseAdapter() {
MouseAdapter mouseAdapter = new MouseAdapter() {
private Sphere sphere;
#Override
public void mousePressed(MouseEvent e) {
if (getComponentAt(e.getX(), e.getY()) instanceof Sphere) {
sphere = (Sphere) getComponentAt(e.getPoint());
}
}
#Override
public void mouseClicked(MouseEvent e) {
Sphere sphere = new Sphere(e.getX(), e.getY());
add(sphere);
System.out.println("MOUSE CLICKED ON EDITORPANEL; SPHERE CREATED!");
revalidate();
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
Point p2 = e.getPoint();
Point loc = sphere.getLocation();
loc.translate(p2.x - sphere.getX(), p2.y - sphere.getY());
sphere.setLocation(loc);
}
#Override
public void mouseReleased(MouseEvent e) {
sphere = null;
System.out.println("Mouse released!");
}
};
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
}
public static EditorPanelView getInstance(Color color, int x, int y, int width, int height) {
return Objects.requireNonNullElseGet(instance, () -> new EditorPanelView(color, x, y, width, height));
}
}
public class Sphere extends JComponent{
private int positionX;
private int positionY;
private final int width = 50;
private final int height = 50;
public Sphere(int x, int y) {
this.positionX = x;
this.positionY = y;
}
#Override
public void paintComponent(Graphics graphics) {
Graphics2D sphere = (Graphics2D) graphics;
sphere.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
sphere.setPaint(Color.BLACK);
sphere.fillOval(positionX -(width/2), positionY -(height/2), width, height);
}
}
public class FrameView extends JFrame {
private static FrameView instance = null;
private static final Color FRAMECOLOR = Color.darkGray;
private FrameView() {
Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
GraphController graphController = GraphController.getInstance();
getContentPane().setBackground(FRAMECOLOR);
setTitle("Graph Editor");
setSize((int) size.getWidth()-200, (int) size.getHeight()-200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
getContentPane().setLayout(new GroupLayout(getContentPane()));
setJMenuBar(MenuBarView.getInstance());
add(EditorPanelView.getInstance(Color.LIGHT_GRAY, 260, 53, getWidth()-270, getHeight()-113));
add(OverviewPanelView.getInstance(Color.gray, 10, 53, 240, getHeight()-113));
add(ButtonPanelView.getInstance(FRAMECOLOR, 5, 5, getWidth()-10, 42));
}
public static JFrame getInstance() {
return Objects.requireNonNullElseGet(instance, FrameView::new);
}
}
public class Panel extends JPanel {
LayoutManager layout;
public Panel(LayoutManager layout, Color color, int x, int y, int width, int height) {
this.layout = layout;
setLayout(this.layout);
setBounds(x, y, width, height);
setBackground(color);
}
}
I tried to change the x and y coordinates in EditorPanelView::addMouseAdapter()
I am working on a game.I want to add images to the applet but they do not appear there until I resize or minimize the applet.I do not know whether my image observer is wrong or not.
The images are in right path.Their in build and class folder with correct spelling. What is wrong with my code?
any help would be much appreciated.
public class game extends JApplet implements KeyListener, Runnable {
ScreenDir sd;
Image ball;
Image flower;
public void init() {
sd = new ScreenDir(this);
ball = getImage(getCodeBase(), "ball.jpg");
ballb = new Ball(50, 50, 30, 40, Color.red, ball, sd);
sd.add(ball);
flower = getImage(getCodeBase(), "flower.gif");
//class Flower is just defined like class Ball
flowerf = new Flower(60, 100, 30, 30, Color.green, flower, sd);
sd.add(flower);
}
public void paint(Graphics g) {
sd.draw(g);
}
//These are for moving the ball
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void run() {
while (true) {
repaint();
try {
Thread.sleep(15);
} catch (InterruptedException ex) {}}}
public class ScreenDir {
ArrayList<Object> olist;
JApplet parent;
public ScreenDir(JApplet parent) {
this.parent = parent;
olist = new ArrayList<>();
}
public void add(Object o) {
olist.add(o);
}
Image Img;
Graphics oG;
Img = parent.createImage(parent.getWidth(), parent.getHeight());
oG = offImg.getGraphics();
for(int i = 0;i<olist.size ();i++){
olist.get(i).draw(offG);
}
g.drawImage (Img,0, 0, parent);
}
public class Ball extends Object {
ScreenDir sd;
public ball(int x, int y, int w, int h, Color c, Image pi, ScreenDir sd) {
{
this.sd = sd;
}
public void draw(Graphics g) {
g.drawImage(pi, x, y, w, h, null);
}
public abstract class Object {
int x, y, w, h;
Color c;
Image pi;
public Object(int x, int y, int w, int h, Color c, Image pi) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
this.pi = pi;
}
public abstract void draw(Graphics g) {
}
Applets load images asynchronously, so it is likely the images are not fully loaded before the applet goes to paint. But every Java component worth mentioning implements an ImageObserver that means it will get updates on image loading. So to fix the problem, change this:
g.drawImage(pi, x, y, w, h, null);
To this:
g.drawImage(pi, x, y, w, h, this);
Update
I mistakenly thought the drawImage method was part of the JApplet (which is an ImageObserver). You might simply change the method declaration & painting line to:
public void draw(Graphics g, ImageObserver io) {
g.drawImage(pi, x, y, w, h, io);
}
Then to call it, change:
sd.draw(g);
To:
sd.draw(g, this);
I have some issues with my paint program in Java.
I have a JComboBox where I can choose to draw either a rectangle or by freehand. The objects are added to an ArrayList. I want to be able to switch between drawing a rectangle and by free hand, and then back to drawing a rectangle, and then by free hand... and so on.
If I do that as the code looks like now, it first draws rectangles fine and then when I switch to free hand it draws lines fine, but then when I switch back to rectangles it still draws lines (or sometimes lines together with weird looking rectangles). The more I switch the weirder it gets.
Can anyone see what is wrong with the code, because I can't?
public abstract class Draw {
public int startX, startY, endX, endY, width, height, w, h;
public String color = "Black";
public Draw(int startX, int startY, int width, int height) {
this.startX = startX;
this.startY = startY;
this.width = width;
this.height = height;
}
public abstract void draw(Graphics2D g);
public int getX() {
return startX;
}
public void setX(int startX) {
this.startX = startX;
}
public int getY() {
return startY;
}
public void setY(int startY) {
this.startY = startY;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public void setColor(String color) {
this.color = color;
}
}
public class Rectangle extends Draw {
public Rectangle(int x, int y, int width, int height) {
super(x, y, width, height);
}
#Override
public void draw(Graphics2D g2) {
g2.drawRect(getX(), getY(), getWidth(), getHeight());
}
}
public class FreeHand extends Draw {
public FreeHand(int x, int y, int width, int height) {
super(x, y, width, height);
}
#Override
public void draw(Graphics2D g2) {
g2.drawLine(getX(), getY(), getWidth(), getHeight());
}
}
public class PaintProgram extends JFrame implements ActionListener {
public ArrayList<Draw> shapeList = new ArrayList<>();
int startX, startY, endX, endY, w, h;
private JPanel topPanel;
private JPanel bottomPanel;
private JPanel leftPanel;
private JPanel rightPanel;
private JComboBox comboBox;
private final String[] boxOptions = new String[] {"Rectangle", "Freehand"};
Container cp = getContentPane();
private int count = 0;
public JavaApplication30(String title) {
super(title);
this.setLayout(new BorderLayout());
this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setSize(840, 500);
this.initComponents();
this.setVisible(true);
}
private void initComponents() {
cp.setBackground(Color.WHITE);
comboBox = new JComboBox(boxOptions);
topPanel = new JPanel();
bottomPanel = new JPanel(new GridLayout(1,2));
rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
leftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
comboBox.setSelectedIndex(0);
comboBox.addActionListener(this);
topPanel.setPreferredSize(new Dimension(0,40));
bottomPanel.setPreferredSize(new Dimension(0,30));
bottomPanel.setBackground(Color.LIGHT_GRAY);
topPanel.add(comboBox);
bottomPanel.add(leftPanel);
bottomPanel.add(rightPanel);
this.add(topPanel, BorderLayout.PAGE_START);
this.add(bottomPanel, BorderLayout.PAGE_END);
}
#Override
public void paint(Graphics g) {
if(count == 0) {
cp.repaint();
}
Graphics2D g2 = (Graphics2D) g;
for (Draw d : shapeList) {
d.draw(g2);
}
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(1));
if (startX != 0 && startY != 0 && endX != 0 && endY != 0) {
int width = Math.abs(startX - endX);
int height = Math.abs(startY - endY);
int minX = Math.min(startX, endX);
int minY = Math.min(startY, endY);
Rectangle r = new Rectangle(minX, minY, width, height);
g2.setPaint(Color.WHITE);
g2.fillRect(r.getX(), r.getY(), r.getWidth(), r.getHeight());
r.setColor(pickedColor);
r.draw(g2);
}
}
#Override
public void actionPerformed(ActionEvent e) {
count++;
if (e.getSource().equals(comboBox)) {
JComboBox cb = (JComboBox)e.getSource();
if (cb.getSelectedItem().equals("Rectangle")) {
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
startX = e.getX();
startY = e.getY();
endX = startX;
endY = startY;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
endX = e.getX();
endY = e.getY();
int width = Math.abs(startX - endX);
int height = Math.abs(startY - endY);
int minX = Math.min(startX, endX);
int minY = Math.min(startY, endY);
Rectangle r = new Rectangle(minX, minY, width, height);
shapeList.add(r);
r.setColor(pickedColor);
startX = 0;
startY = 0;
endX = 0;
endY = 0;
repaint();
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
endX = e.getX();
endY = e.getY();
repaint();
}
});
}
else if (cb.getSelectedItem().equals("Freehand")) {
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
startX = e.getX();
startY = e.getY();
addCoordinate(startX, startY);
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
Graphics g = getGraphics();
Graphics2D g2 = (Graphics2D) g;
FreeHand fh = new FreeHand(startX, startY, e.getX(), e.getY());
shapeList.add(fh);
fh.setColor(pickedColor);
fh.draw(g2);
startX = e.getX();
startY = e.getY();
}
});
}
}
}
public static void main(String args[]) {
new PaintProgram("Paint");
}
}
You add MouseListeners but you do not remove them. Every time you choose something in the combobox, a new listener is added. So when you draw something every listener is applied and weird stuff will happen.
You should remove the previous MouseListener before adding a new one. You might have to remember it in an instance variable.
Alternatively, you can add all listeners at the start, but check the value of the combobox inside the listener. If the value does not correspond to what the listener is for, it should do nothing.
EDIT: Here is how you can remove all listeners
for (MouseListener listener : this.getMouseListeners()) {
this.removeMouseListener(listener);
}
for (MouseMotionListener listener : this.getMouseMotionListeners()) {
this.removeMouseMotionListener(listener);
}
Put this code in before you add the new listeners in the actionPerformed() method
As was stated here and here previously, do not add MouseListeners within your ActionListener, instead, create a single MosueListener and determine what you want to do based on the currently selected item.
Basically, you keep adding a new MouseListener each time actionPerformed is called...they are accumulating...
A solution would be to use a single MouseListener and a factory of some kind...
Start by defining the factory interface...
public interface DrawFactory {
public Draw createDrawing(int x, int y, int width, int height, Color color);
public void addPoint(Draw draw, int x, int y);
}
Create a implementation of the factory for each type of shape you want to draw...
public class RectangleFactory implements DrawFactory {
#Override
public Draw createDrawing(int x, int y, int width, int height, Color color) {
return new Rectangle(x, y, width, height);
}
#Override
public void addPoint(Draw draw, int x, int y) {
// Does nothing...
}
#Override
public boolean isMutable() {
return false;
}
#Override
public String getName() {
return "Rectangle";
}
#Override
public String toString() {
return getName();
}
}
public class FreeHandFactory implements DrawFactory {
#Override
public Draw createDrawing(int x, int y, int width, int height, Color color) {
return new FreeHand(x, y, width, height);
}
#Override
public void addPoint(Draw draw, int x, int y) {
if (draw instanceof FreeHand) {
FreeHand fh = (FreeHand)draw;
//fh.addPoint(x, y);
}
}
#Override
public boolean isMutable() {
return true;
}
#Override
public String getName() {
return "Free Hand";
}
#Override
public String toString() {
return getName();
}
}
Next, create a custom component that extends from JPanel which will act as the primary drawing surface, this will be repsonsible for monitoring the MouseLstener and painting the Draw instances, as was mentioned here
public class DrawSurface extends JPanel {
private DrawFactory factory;
private Draw currentDraw;
private List<Draw> shapeList = new ArrayList<>();
private Color drawColor;
public DrawSurface() {
shapeList = new ArrayList<>(25);
MouseAdapter ma = new MouseAdapter() {
private Point pressPoint;
#Override
public void mousePressed(MouseEvent e) {
pressPoint = e.getPoint();
}
#Override
public void mouseReleased(MouseEvent e) {
DrawFactory factory = getDrawFactory();
if (factory != null) {
Point p = e.getPoint();
if (factory.isMutable() && currentDraw != null) {
factory.addPoint(currentDraw, p.x, p.y);
} else {
int x = Math.min(p.x, pressPoint.x);
int y = Math.min(p.y, pressPoint.y);
int width = Math.abs(p.x - pressPoint.x);
int height = Math.abs(p.y - pressPoint.y);
Draw draw = factory.createDrawing(x, y, width, height, getDrawColor());
shapeList.add(draw);
if (factory.isMutable()) {
currentDraw = draw;
}
}
}
}
};
}
public DrawFactory getDrawFactory() {
return factory;
}
public void setDrawFactory(DrawFactory factory) {
this.factory = factory;
currentDraw = null;
}
public Color getDrawColor() {
return drawColor;
}
public void setDrawColor(Color drawColor) {
this.drawColor = drawColor;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Draw draw : shapeList) {
draw.draw(g2d);
}
g2d.dispose();
}
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
Next, change your boxOptions from String to DrawFactory, this will make it easier to determine which factory you should use. Don't forget to add a reference to the DrawSurface
private final DrawFactory[] boxOptions = new DrawFactory[]{new RectangleFactory(), new FreeHandFactory()};
private DrawSurface drawSurface;
In your initComponents create a new instance of DrawSurface and add it to your frame...
private void initComponents() {
//...
drawSurface = new DrawSurface();
this.add(drawSurface);
}
Change your actionPerformed method to look more like...
#Override
public void actionPerformed(ActionEvent e) {
count++;
drawSurface.setDrawFactory((DrawFactory)comboBox.getSelectedItem());
}
Not sure how you are determining the current color as you example code is incomplete, but basically, you want to set the drawColor of the DrawSurface similarly.
Get rid of the paint method in the PaintProgram as you shouldn't be overriding the paint method of top level containers, which you've been advised against at least once, if not twice.
The point of all this is simple, when you want to add a new "drawing shape", you create a Draw and DrawFactory for it and add the factory to the combo box ... work done...
Alright, so I have two JFrames each with a different implementation of JPanel sitting inside of them. When I call repaint() on the JPanels, what is painted on one JPanel also becomes painted on the other JPanel.
I know I could take care of this by calling something like g.clearRect(), but there are too many components to repaint every time.
Any idea why this is happening?
//This makes the two JFrames and JPanels, sets everything up
public void makeSpaceSimulation() {
int dimen = 10000;
int scale = 20;
int numOfClusters = 30;
int planPerCluster = 2000;
SpaceShip s = new SpaceShip(dimen, scale);
QuadTree t = new QuadTree(dimen, 20);
new PlanetCreationTest(t, dimen, scale, numOfClusters, planPerCluster);
makeMap(dimen, scale, s, t);
makePOV(s, t, scale, dimen);
}
public void makeMap(int dimen, int scale, SpaceShip s, QuadTree t) {
final JFrame f = new JFrame();
f.setSize(dimen / scale, dimen / scale);
f.setLocation(0, 0);
f.setTitle("Map Panel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mapP = new MapPanel(scale, s, dimen);
mapP.setLayout(new BorderLayout());
mapP.addTree(t);
f.add(mapP);
f.setVisible(true);
Insets i = f.getInsets();
f.setSize(dimen / scale + (i.left + i.right) + 2, dimen / scale
+ (i.top + i.bottom) + 2);
Thread th = new Thread() {
public void run() {
while (true) {
mapP.repaint();
}
}
};
th.start();
}
public void makePOV(final SpaceShip s, QuadTree t, int scale, int dimen) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
JFrame f = new JFrame();
f.setSize(500, 500);
f.setLocation(screenSize.width - 500, 0);
f.setTitle("POV Panel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
povP = new POVPanel(s, scale, dimen);
povP.setLayout(new BorderLayout());
povP.addTree(t);
povP.setFocusable(true);
povP.addKeyListener(new KeyListener() {
#Override
public void keyPressed(KeyEvent arg0) {
final int i = arg0.getKeyCode();
Thread th = new Thread() {
public void run() {
if (i == 39) {
s.moveRight();
} else if (i == 37) {
s.moveLeft();
} else if (i == 40) {
s.moveDown();
} else if (i == 38) {
s.moveUp();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
th.start();
}
#Override
public void keyReleased(KeyEvent arg0) {
}
#Override
public void keyTyped(KeyEvent arg0) {
}
});
f.add(povP);
f.setVisible(true);
Insets i = f.getInsets();
f.setSize(dimen / 20 + (i.left + i.right) + 2, dimen / 20
+ (i.top + i.bottom) + 2);
Thread th = new Thread() {
public void run() {
while (true) {
povP.repaint();
}
}
};
th.start();
}
//here's the MapPanel
public class MapPanel extends JPanel {
private QuadTree q;
private int scale;
private int dimen;
private SpaceShip s;
private boolean firstDraw;
public MapPanel(int scale, SpaceShip s, int dimen) {
this.dimen = dimen;
q = new QuadTree(0, 0);
this.scale = scale;
this.s = s;
firstDraw = true;
}
public void addTree(QuadTree q) {
this.q = q;
}
public void paintComponent(Graphics g) {
if (firstDraw) {
q.draw(g, scale, new Point(0, 0));
s.drawScaledGeometry(g);
System.out.println("Totally drew that");
firstDraw = false;
} else {
g.clearRect(s.viewDistance.x/scale, s.viewDistance.y/scale,
s.viewDistance.width/scale, s.viewDistance.height/scale);
q.quadDraw(g, scale, s.viewDistance, new Point(0, 0));
s.drawScaledGeometry(g);
}
}
}
//and this is the POVPanel
public POVPanel(SpaceShip s, int scale, int dimen) {
super();
this.s = s;
// this.scale = scale;
this.dimen = dimen;
}
public void addTree(QuadTree q) {
this.q = q;
}
public void paintComponent(Graphics g) {
g.clearRect(0, 0, dimen / 20, dimen / 20);
q.quadDraw(g, 1, s.viewDistance, s.getMoved());
s.drawGeometry(g);
}
This is (one) of your problems...
public void paintComponent(Graphics g) {
if (firstDraw) {
q.draw(g, scale, new Point(0, 0));
s.drawScaledGeometry(g);
System.out.println("Totally drew that");
firstDraw = false;
} else {
g.clearRect(s.viewDistance.x/scale, s.viewDistance.y/scale,
s.viewDistance.width/scale, s.viewDistance.height/scale);
q.quadDraw(g, scale, s.viewDistance, new Point(0, 0));
s.drawScaledGeometry(g);
}
}
The Graphics context is shared during the paint cycle, meaning when you get it, you will be getting the same context used to paint all the other components on the screen.
You MUST call super.paintComponent(g), which will take care of preparing the graphics context for this component to start painting.
Update #1
Again...
public void paintComponent(Graphics g) {
super.paintComponent(g); // <-- Call me instead...
//g.clearRect(0, 0, dimen / 20, dimen / 20);
q.quadDraw(g, 1, s.viewDistance, s.getMoved());
s.drawGeometry(g);
}
Update #2
I'm also seriously concerned by this
while (true) {
mapP.repaint();
}
This could seriously impact the performance of you application. You DO NOT control the paint cycle, that's the responsibility of the repaint manager. The repaint manager will decide when a repaint is required. Calling repaint repetitively like this could actually cause the repaint manager to delay actually scheduling a paint cycle.
Much better to use a javax.swing.Timer, it's simpler and safer...
Timer timer = new Timer(25, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
mapP.repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
You might find reading through
Performing Custom Painting
Painting in AWT and Swing
Helpful
Update #3
Avoid KeyListener, it will only make you cry. Instead, use the key bindings API
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Drawing multiple pixels/rectangles
In my code i wrote a method that creates a rectangle at mouseX, mouseY. but all it does is update the position of that rectangle so it follows the mouse, i want it to create a new one at the mouse every time the method runs, can someone please help?
this is my method
public void drawParticle(float x, float y){
g.drawRect(x, y, 4, 4);
}
The main class Control call the drawParticle method;
import java.awt.Point;
import java.awt.geom.Point2D;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
public class Control extends BasicGameState {
public static final int ID = 1;
public Methods m = new Methods();
public Graphics g = new Graphics();
int mouseX;
int mouseY;
public void init(GameContainer container, StateBasedGame game) throws SlickException{
}
public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
m.drawParticle(mouseX, mouseY);
}
public void update(GameContainer container, StateBasedGame game, int delta) {
}
public void mousePressed(int button, int x, int y) {
mouseX = x;
mouseY = y;
}
public int getID() {
return ID;
}
}
Thanks - Shamus
The long and short of it is, you need to maintain a list of the objects you want to paint on each paint cycle.
public class ColorMeRectangles {
public static void main(String[] args) {
new ColorMeRectangles();
}
public ColorMeRectangles() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setLayout(new BorderLayout());
frame.add(new RectanglePane());
frame.setVisible(true);
}
});
}
public class RectanglePane extends JPanel {
private Point mousePoint;
private List<Partical> particals;
private Timer generator;
private int min = -4;
private int max = 4;
public RectanglePane() {
setBackground(Color.BLACK);
particals = new ArrayList<Partical>(25);
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
repaint();
}
});
generator = new Timer(125, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
int x = mousePoint.x + (min + (int) (Math.random() * ((max - min) + 1)));
int y = mousePoint.y + (min + (int) (Math.random() * ((max - min) + 1)));
Color color = new Color(
(int) (Math.random() * 255),
(int) (Math.random() * 255),
(int) (Math.random() * 255));
particals.add(new Partical(new Point(x, y), color));
repaint();
}
}
});
generator.setRepeats(true);
generator.setCoalesce(true);
generator.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Partical partical : particals) {
partical.paint(g2d);
}
if (mousePoint != null) {
g2d.setColor(Color.WHITE);
g2d.drawRect(mousePoint.x - 2, mousePoint.y - 2, 4, 4);
}
g2d.dispose();
}
}
public class Partical {
private Point location;
private Color color;
public Partical(Point location, Color color) {
this.location = location;
this.color = color;
}
public Point getLocation() {
return location;
}
public Color getColor() {
return color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(color);
g2d.drawRect(location.x - 4, location.y - 4, 8, 8);
}
}
}
One way to do this is to create a List as a member variable and add a new Rectangle each time the user clicks the mouse. Then in your render() method, iterate through the List of Rectangles and paint each one.