arynaq
arynaq

Reputation: 6870

Swing custom component dummy mouselistener

I am just starting to put together a logging tool for my own use that would log statistics from gym/running and the only experience I have with swing/awt is active rendering for games where you have full control over the Graphics2D object and don't rely on implementing swing components with overriden paints.

Anyway, I was hoping to create a dummy JComponent that I can add to one of my panels (this panel will display graphics, statistics etc depending on what I select from another different sidepanel with options) that does nothing else but listen for mouseevents inside the panel mentioned earlier and draws a selection rectangle on mousedrags so that I can zoom in the data if higher resolutions exist. I just don't know how, I have added the component to the panel but it registers nothing inside the panel, instead it seems to have a local space that is limited to the bottom of the panel/frame.

Here is the component

package gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JComponent;

@SuppressWarnings("serial")
public class MarkerRectangle extends JComponent implements MouseListener,
        MouseMotionListener {

private boolean draw;
private int startX, endX, startY, endY;
private Color color = new Color(0, 255, 0, 100);

public MarkerRectangle(int width, int height) {
    setPreferredSize(new Dimension(width, height));
    this.addMouseListener(this);
    this.addMouseMotionListener(this);
}

@Override
public void mouseClicked(MouseEvent e) {
    System.out.println("Mouse clicked@ " + e.getX() + "," + e.getY());
}

@Override
public void mouseEntered(MouseEvent e) {
    System.out.println("Mouse entered @ " + e.getX() + "," + e.getY());

}

@Override
public void mouseExited(MouseEvent e) {
    System.out.println("Mouse left @ " + e.getX() + "," + e.getY());
}

@Override
public void mousePressed(MouseEvent e) {
    System.out.println("Mouse pressed@ " + e.getX() + "," + e.getY());
}

@Override
public void mouseReleased(MouseEvent e) {
    System.out.println("Mouse was released @ " + e.getX() + "," + e.getY());
}

@Override
public void mouseDragged(MouseEvent e) {
    System.out.println("Mouse being dragged, currently@ " + e.getX() + ","
            + e.getY());

}

@Override
public void mouseMoved(MouseEvent e) {
    System.out.println("I registered a move@ " + e.getX() + "," + e.getY());
}

@Override
// Draw rectangle
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    g2d.dispose();
}

}

The panel

public class GraphPane extends JPanel {


public GraphPane(Graph graph) {
    this.add(graph);
    this.add(new MarkerRectangle(800, 600));
    this.setBackground(Color.gray);
    this.setPreferredSize(new Dimension(800, 600));
}


}

The graph, holds random data atm

public class Graph extends JComponent {
private GeneralPath data;
private Stroke stroke;

public Graph() {
    this.data = new GeneralPath();
    this.stroke = new BasicStroke(3f);
    this.setPreferredSize(new Dimension(750, 550));
    init();
}

private void init() {
    data.moveTo(0, 0);
    double cntr = 0;
    double[][] points = new double[10][1];
    for (double[] point : points) {
        cntr += 100;
        point[0] = Math.random() * 100;
        point[1] = Math.random() * 100;
        data.lineTo(cntr, point[1]);
    }
}

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g.create();
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setStroke(stroke);
    g2d.draw(data);
    g2d.dispose();
}
}

I want to implement something like the above because eventually I imagine the GUI to become quite complex and simple events like drawing a rectangle to mark data should not be in the main controller (so as to prevent a lot of if-tests and clutter in code).

Screenshot of what I get: enter image description here

What I want: enter image description here

EDIT While the accepted answer below is the better solution I am posting this in the event that someone may want to use it. It will not work if you resize smaller the prefferedSize.

public class Test {

public static void main(String[] args) {
    GeneralJFrame frame = new GeneralJFrame(1200, 800);
    frame.addPanel(new GraphPane(new Graph()), BorderLayout.CENTER);
    frame.addPanel(new ButtonPane(), BorderLayout.SOUTH);
    frame.addPanel(new ButtonPane(), BorderLayout.SOUTH);
    frame.addPanel(new ButtonPane(), BorderLayout.WEST);
    frame.addPanel(new ButtonPane(), BorderLayout.EAST);
    frame.addPanel(new ButtonPane(), BorderLayout.NORTH);
    frame.start();
}
}

public class GraphPane extends JPanel {


public GraphPane(Graph graph) {
    GridBagConstraints c = new GridBagConstraints();
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = GridBagConstraints.REMAINDER;
    c.gridx = 0;
    c.gridy = 0;
    this.setLayout(new GridBagLayout());
    this.add(graph);
    this.add(new MarkerRectangle(), c);
    this.setBackground(new Color(205, 201, 201));
}



}

public class MarkerRectangle extends JComponent implements MouseListener,
    MouseMotionListener {

private boolean draw;
private int startX, endX, startY, endY;

public MarkerRectangle() {
    this.addMouseListener(this);
    this.addMouseMotionListener(this);
    setOpaque(false);
    setPreferredSize(new Dimension(800, 600));
}

@Override
public void mouseClicked(MouseEvent e) {

}

@Override
public void mouseEntered(MouseEvent e) {

}

@Override
public void mouseExited(MouseEvent e) {

}

@Override
public void mousePressed(MouseEvent e) {
    startX = e.getX();
    startY = e.getY();
    draw = true;
}

@Override
public void mouseReleased(MouseEvent e) {
    draw = false;
    repaint();
}

@Override
public void mouseDragged(MouseEvent e) {
    endX = e.getX();
    endY = e.getY();
    repaint();
}

@Override
public void mouseMoved(MouseEvent e) {

}

@Override
// Draw rectangle
protected void paintComponent(Graphics g) {
    System.out.println(getSize());
    if (!draw)
        return;
    int w = endX-startX;
    int h = endY - startY;
    Graphics2D g2d = (Graphics2D) g.create();
    g2d.setColor(new Color(255, 165, 0));
    g2d.fillRect(startX, startY, w, h);
    g2d.dispose();
}

}

public class ButtonPane extends JPanel {
public ButtonPane() {
    add(new JButton("HELLO"));
    setBackground(Color.gray);
    setBorder(BorderFactory.createEtchedBorder(Color.white,
            Color.gray.darker()));
}
}

public class GeneralJFrame {
private JFrame frame;

public GeneralJFrame(int width, int height) {
    this.frame = new JFrame("Le Muscles");
    this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public void addPanel(JPanel panel, String location) {
    this.frame.add(panel, location);
}

public void start() {
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
}
}

Outputs: (orange is dragged with mouse) enter image description here

Upvotes: 2

Views: 1337

Answers (1)

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285450

I still don't fully understand your question. As I see it, you've

  • created several JPanels
  • Given one JPanel a MouseListener and MouseMotionListener
  • You've added that JPanel to the bottom of another JPanel.
  • The JPanel that's sitting on the bottom registers all mouse events as it has been told to do
  • So your program is behaving as expected based on your code.

  • The question I have is this: how is it not behaving as you expect it to?

  • If you expect that adding a JPanel with MouseListeners and MouseMotionListeners attached will result in all the JPanels of the GUI to be listened to, well of course that won't happen. To have more of the GUI register mouse events, you'll have to add MouseListeners and MouseMotionListeners to those components. And that is my answer so far to your question as I see it. If I didn't answer the true question you currently face, then please clarify it for us.

You state:

What I want is an invisible (transparent) panel on top of the blue one in the above screenshot that is just as large as the blue one, not a subpanel that is sitting in the bottom. I want the blue one to contain this one (should not be a problem since it is just a jcomponent). What I hope to achieve is a sort over "invisible" overlay that registers mousevents so I don't have to implement these events in the blue panel.

Consider using a JLayer for this. As per the JLayer API:

JLayer is a good solution if you only need to do custom painting over compound component or catch input events from its subcomponents.


OK, I've experimented with it a bit and came up with something like this:

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Path2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;

import javax.swing.*;
import javax.swing.plaf.LayerUI;

@SuppressWarnings("serial")
public class GraphPane2 extends JPanel {
   private static final int GRAPH_WIDTH = 1000;
   private static final int GRAPH_HEIGHT = 750;
   private Graph2 graph2 = new Graph2(GRAPH_WIDTH, GRAPH_HEIGHT);

   public GraphPane2() {
      LayerUI<Graph2> myLayerUI = new MyLayerUI<Graph2>();
      JLayer<Graph2> panelLayer = new JLayer<Graph2>(graph2, myLayerUI);
      setLayout(new BorderLayout());
      add(panelLayer);

      myLayerUI.addPropertyChangeListener(new PropertyChangeListener() {

         @Override
         public void propertyChange(PropertyChangeEvent evt) {
            if (MyLayerUI.MOUSE_RELEASED.equals(evt.getPropertyName())) {
               Rectangle rect = (Rectangle) evt.getNewValue();
               System.out.println(rect);
            }
         }
      });
   }

   private static void createAndShowGui() {
      GraphPane2 mainPanel = new GraphPane2();

      JFrame frame = new JFrame("Graph Pane2");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.setResizable(false);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

@SuppressWarnings("serial")
class MyLayerUI<V extends JComponent> extends LayerUI<V> {
   private static final Color FILL_COLOR = new Color(0, 128, 0, 128);
   public static final String MOUSE_RELEASED = "mouse released";
   private Point pressedPt;
   private Point draggedPt;
   private Rectangle rect;

   @Override
   public void paint(Graphics g, JComponent c) {
      super.paint(g, c);
      if (rect != null) {
         Graphics2D g2 = (Graphics2D) g;
         g2.setColor(FILL_COLOR);
         g2.fill(rect);
      }
   }

   public void installUI(JComponent c) {
      super.installUI(c);
      ((JLayer) c).setLayerEventMask(AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
   }

   public void uninstallUI(JComponent c) {
      super.uninstallUI(c);
      ((JLayer)c).setLayerEventMask(0);
   }

   @Override
   public void eventDispatched(AWTEvent e, JLayer<? extends V> l) {
      MouseEvent mEvt = (MouseEvent) e;
      int id = mEvt.getID();
      int btn = mEvt.getButton();
      if (id == MouseEvent.MOUSE_PRESSED && btn == MouseEvent.BUTTON1) {
         pressedPt = mEvt.getPoint();
         rect = new Rectangle(pressedPt.x, pressedPt.y, 0, 0);
      }
      if (id == MouseEvent.MOUSE_PRESSED && btn != MouseEvent.BUTTON1) {
         pressedPt = null;
      }
      if (id == MouseEvent.MOUSE_DRAGGED && pressedPt != null) {
         draggedPt = mEvt.getPoint();
         int x = Math.min(draggedPt.x, pressedPt.x);
         int y = Math.min(draggedPt.y, pressedPt.y);
         int width = Math.abs(draggedPt.x - pressedPt.x);
         int height = Math.abs(draggedPt.y - pressedPt.y);
         rect = new Rectangle(x, y, width, height);
      }
      if (id == MouseEvent.MOUSE_RELEASED && pressedPt != null) {
         draggedPt = mEvt.getPoint();
         int x = Math.min(draggedPt.x, pressedPt.x);
         int y = Math.min(draggedPt.y, pressedPt.y);
         int width = Math.abs(draggedPt.x - pressedPt.x);
         int height = Math.abs(draggedPt.y - pressedPt.y);
         rect = new Rectangle(x, y, width, height);
         firePropertyChange(MOUSE_RELEASED, null, rect);
      }      
      l.repaint();
   }
}

@SuppressWarnings("serial")
class Graph2 extends JPanel {
   private static final int MAX_DATA_POINTS = 100;
   private static final int STEP = 10;
   private static final Stroke STROKE = new BasicStroke(3f);
   private Path2D path2D;
   private int width;
   private int height;
   private int[] data = new int[MAX_DATA_POINTS + 1];
   private Random random = new Random();

   public Graph2(int width, int height) {
      this.width = width;
      this.height = height;
      init();

      addComponentListener(new ComponentAdapter() {
         @Override
         public void componentResized(ComponentEvent e) {
            path2D = new Path2D.Double();
            int w = getWidth();
            int h = getHeight();
            double x = 0;
            double y = ((double) MAX_DATA_POINTS - data[0]) * h
                  / MAX_DATA_POINTS;
            path2D.moveTo(x, y);
            for (int i = 1; i < data.length; i++) {
               x = (i * w) / (double) MAX_DATA_POINTS;
               y = ((double) MAX_DATA_POINTS - data[i]) * h
                     / (double) MAX_DATA_POINTS;
               path2D.lineTo(x, y);
            }
         }
      });
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(width, height);
   }

   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (path2D != null) {
         Graphics2D g2d = (Graphics2D) g;
         g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
               RenderingHints.VALUE_ANTIALIAS_ON);
         g2d.setStroke(STROKE);
         g2d.draw(path2D);
      }
   };

   private void init() {
      // create and fill random data
      data[0] = 0;
      boolean up = true;
      // max and min data values -- used for normalization
      int min = Integer.MAX_VALUE;
      int max = Integer.MIN_VALUE;
      for (int i = 1; i < data.length; i++) {
         up = random.nextInt(4) < 3 ? up : !up;
         if (up) {
            data[i] = data[i - 1] + random.nextInt(STEP);
         } else {
            data[i] = data[i - 1] - random.nextInt(STEP);
         }

         if (data[i] > max) {
            max = data[i];
         }
         if (data[i] < min) {
            min = data[i];
         }
      }

      // normalize the data
      for (int i = 0; i < data.length; i++) {
         int datum = (MAX_DATA_POINTS * (data[i] - min)) / (max - min);
         data[i] = datum;
      }

   }
}

This will look like:

enter image description here

Upvotes: 2

Related Questions