Search code examples
javaswingzoomingjcheckbox

Make ZoomBox around cursor disappear when JCheckBox unchecked


I used the first answer of Zoom box for area around mouse location on screen to create a Zoom Box Window around mouse location that would zoom into images as the mouse moves.

Now, I wanna know how to activate this Zoom Box View when a JCheckBox is checked and desactivate it if it's unchecked.

I modified the classes ZoomBoxWindow and ZoomPane written by @MadProgrammer by adding the lines of code for activating and desactivating the ZoomBoxView, but this doesn't seem to work. Could you please tell me what am I doing wrong?

Here is the code:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.JCheckBox;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent; 
import java.awt.event.KeyEvent;

public class ZoomPane extends JPanel {

protected static final int ZOOM_AREA = 40;

private JComponent parent;
private JWindow popup;
private Boolean zoomBoxActivated = false;

private BufferedImage buffer;

private float zoomLevel = 2f;

public ZoomPane(JComponent parent, Boolean zba) {
  this.parent = parent;
  this.zoomBoxActivated=zba;
  popup = new JWindow();
  popup.setLayout(new BorderLayout());
  popup.add(this);
  popup.pack();
  popup.setAlwaysOnTop(true);
  MouseAdapter ma = new MouseAdapter() {
    @Override
    public void mouseMoved(MouseEvent e) {
      Point p = e.getPoint();
      Point pos = e.getLocationOnScreen();
      updateBuffer(p);
      popup.setLocation(pos.x - 20, pos.y + 20);
      repaint();
    }

    @Override
    public void mouseEntered(MouseEvent e) {

  if(zoomBoxActivated){
     popup.setVisible(true);
  }
  else {
     popup.setVisible(false);
      }
    }

    @Override
    public void mouseExited(MouseEvent e) {
      popup.setVisible(false);
    }

  };

  parent.addMouseListener(ma);
  parent.addMouseMotionListener(ma);
}

protected void updateBuffer(Point p) {
  int width = Math.round(ZOOM_AREA);
  int height = Math.round(ZOOM_AREA);
  buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
  Graphics2D g2d = buffer.createGraphics();
  AffineTransform at = new AffineTransform();

  int xPos = (ZOOM_AREA / 2) - p.x;
  int yPos = (ZOOM_AREA / 2) - p.y;

  if (xPos > 0) {
    xPos = 0;
  }
  if (yPos > 0) {
    yPos = 0;
  }

  if ((xPos * -1) + ZOOM_AREA > parent.getWidth()) {
    xPos = (parent.getWidth() - ZOOM_AREA) * -1;
  }
  if ((yPos * -1) + ZOOM_AREA > parent.getHeight()) {
    yPos = (parent.getHeight()- ZOOM_AREA) * -1;
  }

  at.translate(xPos, yPos);
  g2d.setTransform(at);
  parent.paint(g2d);
  g2d.dispose();
}

@Override
public Dimension getPreferredSize() {
  return new Dimension(Math.round(ZOOM_AREA * zoomLevel), Math.round(ZOOM_AREA * zoomLevel));
}

@Override
protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  Graphics2D g2d = (Graphics2D) g.create();
  if (buffer != null) {
    AffineTransform at = g2d.getTransform();
    g2d.setTransform(AffineTransform.getScaleInstance(zoomLevel, zoomLevel));
    g2d.drawImage(buffer, 0, 0, this);
    g2d.setTransform(at);
  }
  g2d.setColor(Color.RED);
  g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
  g2d.dispose();
}

}

public class TestPane extends JPanel {

private BufferedImage img;

public TestPane() {
  try {
    img = ImageIO.read(new File("satellite-image-of-spain.jpg"));
  } catch (IOException ex) {
    ex.printStackTrace();
  }
}

@Override
public Dimension getPreferredSize() {
  return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}

@Override
protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  if (img != null) {
    Graphics2D g2d = (Graphics2D) g.create();
    int x = (getWidth() - img.getWidth()) / 2;
    int y = (getHeight() - img.getHeight()) / 2;
    g2d.drawImage(img, x, y, this);
    g2d.dispose();
  }
}
}

public class ZoomBoxWindow {

 ZoomPane zoomPane;

public static void main(String[] args) {
 new ZoomBoxWindow();
}

public ZoomBoxWindow() {
EventQueue.invokeLater(new Runnable() {
  @Override
  public void run() {
    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
      ex.printStackTrace();
    }

    TestPane pane = new TestPane();
    zoomPane = new ZoomPane(pane, false);

JPanel buttonPanel = new JPanel(new BorderLayout());

JCheckBox zoomBoxChkBox = new JCheckBox("Zoom Box");
zoomBoxChkBox.setMnemonic(KeyEvent.VK_Z);
zoomBoxChkBox.setSelected(false);
zoomBoxChkBox.addItemListener(new ItemListener() {

@Override
public void itemStateChanged(ItemEvent e) {
    // TODO Auto-generated method stub
    if ( e.getStateChange() == ItemEvent.SELECTED) {
         zoomPane = new ZoomPane(pane,true);
    }
    else {

         zoomPane = new ZoomPane(pane,false);
    }
}

});
buttonPanel.add(zoomBoxChkBox);

    JFrame frame = new JFrame("Testing");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(pane,BorderLayout.CENTER);
frame.add(buttonPanel, BorderLayout.PAGE_START);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }
});
}
}

Thank you for your answer.


Solution

  • So,

    zoomBoxChkBox.addItemListener(new ItemListener() {
        @Override
        public void itemStateChanged(ItemEvent e) {
            // TODO Auto-generated method stub
            if (e.getStateChange() == ItemEvent.SELECTED) {
                zoomPane = new ZoomPane(pane, true);
            } else {
    
                zoomPane = new ZoomPane(pane, false);
            }
        }
    });
    

    Isn't really doing anything, you're just create a new instance of ZoomPane. Apart from adding a bunch of MouseListeners to the parent component, which could cause you no end of issues.

    Instead, I'd add a new method to ZoomPane

    public class ZoomPane extends JPanel {
    
        private boolean isAutoDisplayEnabled = false;
        //...
    
    
        public void setShowZoomPopup(boolean show) {
            popup.setVisible(show);
            isAutoDisplayEnabled = show;
        }
    

    This now allows you to control the visibility state of the popup externally.

    Now, the isAutoDisplayEnabled flag is simply used to determine if the popup should be displayed when the mouseEntered event is triggered, for example...

    MouseAdapter ma = new MouseAdapter() {
        //...
        @Override
        public void mouseEntered(MouseEvent e) {
            if (isAutoDisplayEnabled) {
            popup.setVisible(true);
            }
        }
    

    Now your ItemListener can control the state of the popup

    zoomBoxChkBox.addItemListener(new ItemListener() {
        @Override
        public void itemStateChanged(ItemEvent e) {
            // TODO Auto-generated method stub
            if (e.getStateChange() == ItemEvent.SELECTED) {
                zoomPane.setShowZoomPopup(true);
            } else {
                zoomPane.setShowZoomPopup(false);
            }
        }
    });
    

    You could also add a check to see if the mouse is currently within the bounds of the image pane, so you don't show it needlessly, but I'll leave that to you to try and figure out ;)

    the popup is flickering as it is being moved

    This is, because every time the popup is displayed, it triggers a mouseExit event, which triggers the popup to be hidden, which then triggers a mouseEnter event and so on and so forth...

    This is a slightly different take on the same idea, but, instead of a separate window, this paints the zoom as part of the image pane itself.

    This does mean that, if the zoom falls beyond the bounds of the panel, it will be truncated, but I've spent some time so that you can resize the panel larger than the image and the zoom effect will still work

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.event.MouseAdapter;
    import java.awt.event.MouseEvent;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.Area;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import javax.imageio.ImageIO;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class ZoomBoxWindow {
    
        public static void main(String[] args) {
            new ZoomBoxWindow();
        }
    
        public ZoomBoxWindow() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }
    
                    TestPane pane = new TestPane();
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(pane);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            private BufferedImage img;
            private Point zoomPoint;
            private boolean zoomEnabled = true;
            private int zoomArea = 80;
            private float zoom = 2.0f;
    
            public TestPane() {
                try {
                    img = ImageIO.read(new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/_cg_1009___Afraid___by_Serena_Clearwater.png"));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
    
                addMouseMotionListener(new MouseAdapter() {
                    @Override
                    public void mouseMoved(MouseEvent e) {
                        zoomPoint = e.getPoint();
                        repaint();
                    }
                });
    
                addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseEntered(MouseEvent e) {
                        repaint();
                    }
    
                    @Override
                    public void mouseExited(MouseEvent e) {
                        zoomPoint = null;
                        repaint();
                    }
    
                });
            }
    
            public float getZoom() {
                return zoom;
            }
    
            public void setZoom(float zoom) {
                this.zoom = zoom;
                repaint();
            }
    
            public int getZoomArea() {
                return zoomArea;
            }
    
            public void setZoomArea(int zoomArea) {
                this.zoomArea = zoomArea;
                repaint();
            }
    
            public boolean isZoomEnabled() {
                return zoomEnabled;
            }
    
            public void setZoomEnabled(boolean zoomEnabled) {
                this.zoomEnabled = zoomEnabled;
                repaint();
            }
    
            @Override
            public Dimension getPreferredSize() {
                return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
            }
    
            protected Point getOffset() {
                if (img == null) {
                    return new Point(0, 0);
                }
                int x = (getWidth() - img.getWidth()) / 2;
                int y = (getHeight() - img.getHeight()) / 2;
                return new Point(x, y);
            }
    
            protected Rectangle getImageBounds() {
                Rectangle bounds = new Rectangle(0, 0, 0, 0);
                if (img != null) {
                    bounds.setLocation(getOffset());
                    bounds.setSize(img.getWidth(), img.getHeight());
                }
                return bounds;
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                if (img != null) {
                    Graphics2D g2d = (Graphics2D) g.create();
                    Point offset = getOffset();
                    g2d.drawImage(img, offset.x, offset.y, this);
                    if (zoomPoint != null) {
                        BufferedImage zoomBuffer = updateBuffer(zoomPoint);
                        if (zoomBuffer != null) {
                            Rectangle bounds = getZoomBounds();
                            g2d.drawImage(zoomBuffer, bounds.x, bounds.y, this);
                            g2d.setColor(Color.RED);
                            g2d.draw(bounds);
                        }
                    }
                    g2d.dispose();
                }
            }
    
            protected Rectangle getZoomBounds() {
                Rectangle bounds = null;
                if (zoomPoint != null && img != null) {
                    int zoomArea = getZoomArea();
                    int xPos = zoomPoint.x - (zoomArea / 2);
                    int yPos = zoomPoint.y - (zoomArea / 2);
    
                    Rectangle zoomBounds = new Rectangle(xPos, yPos, zoomArea, zoomArea);
                    Rectangle imageBounds = getImageBounds();
    
                    bounds = imageBounds.intersection(zoomBounds);
                    System.out.println(bounds);
                }
                return bounds;
            }
    
            protected BufferedImage updateBuffer(Point p) {
                if (zoomPoint == null) {
                    return null;
                }
                Rectangle bounds = getZoomBounds();
                Point offset = getOffset();
                bounds.translate(-offset.x, -offset.y);
                if (bounds.x < 0 || bounds.y < 0 || bounds.width <= 0 || bounds.height <= 0) {
                    return null;
                }
                BufferedImage zoomBuffer = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2d = zoomBuffer.createGraphics();
    
                BufferedImage sample = img.getSubimage(bounds.x, bounds.y, bounds.width, bounds.height);
                double zoom = getZoom();
                Image scaled = sample.getScaledInstance((int) (bounds.width * zoom), (int) (bounds.height * zoom), Image.SCALE_SMOOTH);
    
                g2d.drawImage(scaled, 0, 0, this);
                g2d.dispose();
                return zoomBuffer;
            }
    
        }
    
    }