0

I'm working on a game in Java and have the following challenge.

I have 2 JPanels and need to visually drag shapes from one JPanel to another. I've got this working using the GlassPane from the JFrame. When I press the mouse to drag a shape, the GlassPane activates and transfers the shape to the glassPane. Because of this you need to transfer the mousePressed state from the JPanels mouseAdapter to the glassPanes mouseAdapter. I solved this by using the Robot Class which simulates another mousePressed event after the glassPane has been acivated.

Now here comes the problem, this workaround only works on windows and not on mac osx, on osx the mouse keeps talking to the JPanels mouseAdapter as long as the mousebutton is pressed. So does anyone know how to transfer the mousePressed state from one mouseAdapter to another while pressing the mousebutton, in a proper way? (Releasing the button and pressing it again is not an option as this would defeat the purpose of dragging.)

Jason Plank
  • 2,336
  • 5
  • 31
  • 40
Jesse
  • 1,332
  • 2
  • 14
  • 25
  • 1
    it seems like you are trying to reimplement java's drag and drop mechanisms. Have you tried using that instead? – MeBigFatGuy Apr 08 '11 at 21:42
  • @MeBigFatGuy Well, shapes don't support dnd out of the box, so I would need to implement it, which would be a lot work. (especialy since I don't know how to do that yet) And I've invested quite some time in this dragging method, so I would hate to abandon it... – Jesse Apr 08 '11 at 21:54
  • understood. but i'm guessing it would be easier to make your shapes into components, then to try to jump through the hoops your trying to do. good luck! – MeBigFatGuy Apr 08 '11 at 22:08
  • @MeBigFatGuy For my next little project I'm going to dump this drag method for sure and going to focus on the dnd and transferable stuff. Thanks for your comments. – Jesse Apr 08 '11 at 22:14
  • @MeBigFatGuy What exactly did you mean with "make your shapes into components" ? – Jesse Apr 08 '11 at 22:29
  • Well create a class that is derived from JComponent. Override paintComponent to draw the shape. You will also have to override some other methods for sizing, etc. Then these components can be added directly to the panels you mention above. From the sound of what you are doing, you will probably need to use a null layout for those panels. – MeBigFatGuy Apr 09 '11 at 01:18

1 Answers1

2

Why not add the MouseListener just to the glasspane, and in the mousePressed method, get the mouse's location (Point) and then get the draggable component by calling getComponentAt(Point p) on the Container that holds your Component? You can then place the component into the glasspane and drag it there. For instance here is one way I've done this using a JLayeredPane (which is similar to using a glasspane): DragLabelOnLayeredPane

Or another thought: why not simply add the MouseAdapter on the dragged component itself and leave it on the component? As long as you take care to get mouse position relative to the screen and move the component relative to its container, you should have no problem whether the component is in the contentPane or the glasspane.

edit: or go with MeBigFatGuy's excellent suggestion.

edit 2: A semi-drunk late at night attempt at a not-brief-as-I'd-like proof of concept program, a program that moves shapes by adding a MouseListener to the glass pane only.

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.*;

@SuppressWarnings("serial")
public class DragShapesMainPanel extends JPanel {
   private static final Dimension RIGHT_PANEL_SIZE = new Dimension(300, 450);
   private static final int SHAPE_COUNT = 10;
   private static final int SHAPE_WIDTH = 40;
   private static final int SHAPE_HEIGHT = SHAPE_WIDTH;
   private Shape draggedShape = null;
   private DragShapesPanel leftPanel = new DragShapesPanel(Color.blue, Color.black);
   private DragShapesPanel rightPanel = new DragShapesPanel(Color.blue, Color.black);
   private DragShapesGlassPanel glassPanel = new DragShapesGlassPanel(Color.pink, Color.gray);
   private Random random = new Random();

   public DragShapesMainPanel() {
      setLayout(new GridLayout(1, 0));
      setBackground(Color.black);
      rightPanel.setPreferredSize(RIGHT_PANEL_SIZE);
      leftPanel.setPreferredSize(RIGHT_PANEL_SIZE);
      rightPanel.setBorder(BorderFactory.createLineBorder(Color.black, 1));
      leftPanel.setBorder(BorderFactory.createLineBorder(Color.black, 1));
      add(leftPanel);
      add(rightPanel);

      MouseAdapter myMouseAdapter = new MyMouseAdapter();
      glassPanel.addMouseListener(myMouseAdapter);
      glassPanel.addMouseMotionListener(myMouseAdapter);

      glassPanel.setOpaque(false);
      glassPanel.setVisible(true);

      for (int i = 0; i < SHAPE_COUNT; i++) {
         leftPanel.addShape(createRandomShape(i));
      }
   }

   private Shape createRandomShape(int i) {
      Dimension size = rightPanel.getPreferredSize();
      int x = random.nextInt(size.width - SHAPE_WIDTH);
      int y = random.nextInt(size.height - SHAPE_HEIGHT);
      switch (i % 3) {
      case 0:
         return new Ellipse2D.Double(x, y, SHAPE_WIDTH, SHAPE_HEIGHT);

      case 1:
         return new Rectangle2D.Double(x, y, SHAPE_WIDTH, SHAPE_HEIGHT);

      case 2:
         return new RoundRectangle2D.Double(x, y, SHAPE_WIDTH, SHAPE_HEIGHT, 15, 15);

      default:
         break;
      }
      return null;
   }

   public JPanel getGlassPanel() {
      return glassPanel;
   }

   private class MyMouseAdapter extends MouseAdapter {
      Point initialLocation = null;

      @Override
      public void mousePressed(MouseEvent e) {
         if (e.getButton() != MouseEvent.BUTTON1) {
            return;
         }
         Component componentAt = getComponentAt(e.getPoint());
         if (!(componentAt instanceof DragShapesPanel)) {
            return;
         }

         initialLocation = e.getPoint();
         DragShapesPanel dsPanel = (DragShapesPanel) getComponentAt(initialLocation);

         int x = initialLocation.x - dsPanel.getLocation().x;
         int y = initialLocation.y - dsPanel.getLocation().y;
         Point p = new Point(x, y);

         Shape shape = dsPanel.getShapeAtPoint(p);
         if (shape == null) {
            initialLocation = null;
            return;
         }

         dsPanel.removeShape(shape);
         dsPanel.repaint();

         int tx = dsPanel.getLocation().x;
         int ty = dsPanel.getLocation().y;
         draggedShape = AffineTransform.getTranslateInstance(tx, ty).createTransformedShape(shape);

         glassPanel.setShape(draggedShape);
         glassPanel.repaint();
      }

      @Override
      public void mouseDragged(MouseEvent e) {
         if (initialLocation == null) {
            return;
         }

         Point currentLocation = e.getPoint();
         int x = currentLocation.x - initialLocation.x;
         int y = currentLocation.y - initialLocation.y;

         glassPanel.translate(new Point(x, y));
         glassPanel.repaint();
      }

      @Override
      public void mouseReleased(final MouseEvent e) {
         if (initialLocation == null) {
            return;
         }
         SwingUtilities.invokeLater(new Runnable() {
            public void run() {
               Component componentAt = getComponentAt(e.getPoint());
               if (!(componentAt instanceof DragShapesPanel)) {
                  return;
               }

               DragShapesPanel dsPanel = (DragShapesPanel) getComponentAt(e.getPoint());

               Point currentLocation = e.getPoint();
               int x = currentLocation.x - initialLocation.x;
               int y = currentLocation.y - initialLocation.y;

               Point dspPoint = dsPanel.getLocation();
               int tx = x - dspPoint.x;
               int ty = y - dspPoint.y;
               draggedShape = AffineTransform.getTranslateInstance(tx, ty).createTransformedShape(
                        draggedShape);

               dsPanel.addShape(draggedShape);
               dsPanel.repaint();

               initialLocation = null;
               glassPanel.setShape(null);
               glassPanel.translate(new Point(0, 0));
            }
         });
      }
   }

   private static void createAndShowUI() {
      DragShapesMainPanel dragShapesMain = new DragShapesMainPanel();
      JFrame frame = new JFrame("DragShapes");
      frame.getContentPane().add(dragShapesMain);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.pack();
      frame.setGlassPane(dragShapesMain.getGlassPanel());
      frame.getGlassPane().setVisible(true);
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      java.awt.EventQueue.invokeLater(new Runnable() {
         public void run() {
            createAndShowUI();
         }
      });
   }
}

@SuppressWarnings("serial")
class DragShapesPanel extends JPanel {
   private static final float STROKE_WIDTH = 3;
   private static final Stroke SHAPE_STROKE = new BasicStroke(STROKE_WIDTH);
   private List<Shape> shapeList = new ArrayList<Shape>();
   private Color shapeFillColor;
   private Color shapeBorderColor;

   public DragShapesPanel(Color fillColor, Color borderColor) {
      this.shapeFillColor = fillColor;
      this.shapeBorderColor = borderColor;
   }

   public void addShape(Shape s) {
      shapeList.add(s);
   }

   public void removeShape(Shape s) {
      shapeList.remove(s);
   }

   public Shape getShapeAtPoint(Point p) {
      Shape shapeAtPoint = null;
      for (int i = shapeList.size() - 1; i >= 0; i--) {
         if (shapeList.get(i).contains(p)) {
            return shapeList.get(i);
         }
      }
      return shapeAtPoint;
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      Graphics2D g2 = (Graphics2D) g;
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      for (Shape shape : shapeList) {
         g2.setColor(shapeFillColor);
         g2.fill(shape);
         g2.setStroke(SHAPE_STROKE);
         g2.setColor(shapeBorderColor);
         g2.draw(shape);
      }
   }
}

@SuppressWarnings("serial")
class DragShapesGlassPanel extends JPanel {
   private static final float STROKE_WIDTH = 1.5f;
   private static final Stroke SHAPE_STROKE = new BasicStroke(STROKE_WIDTH);
   private Shape shape = null;
   private Color shapeFillColor;
   private Color shapeBorderColor;
   private AffineTransform transform = new AffineTransform();

   public DragShapesGlassPanel(Color fillColor, Color borderColor) {
      this.shapeFillColor = fillColor;
      this.shapeBorderColor = borderColor;
   }

   public void setShape(Shape shape) {
      this.shape = shape;
   }

   public void translate(Point p) {
      transform = AffineTransform.getTranslateInstance(p.x, p.y);
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (shape == null) {
         return;
      }
      Graphics2D g2 = (Graphics2D) g;
      g2.transform(transform);
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2.setColor(shapeFillColor);
      g2.fill(shape);
      g2.setStroke(SHAPE_STROKE);
      g2.setColor(shapeBorderColor);
      g2.draw(shape);
   }
}
Community
  • 1
  • 1
Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • That's an interesting thought you have there, I'm going to try that out. thnx. – Jesse Apr 08 '11 at 22:17
  • You can't add mouseListeners to Shape objects, any suggestions? – Jesse Apr 08 '11 at 23:56
  • @Jesse: create and post an [SSCCE](http://sscce.org) for your best chances of getting decent help fast (check the link). – Hovercraft Full Of Eels Apr 09 '11 at 02:40
  • @Jesse: edit 2 shows my poor attempt at an SSCCE (too big), but it might help as proof of concept. – Hovercraft Full Of Eels Apr 09 '11 at 06:27
  • thnx for the code, or should I say complete application, my work is done here! ;) It's interesting to see how you translate the points from the glassPane to the panel below, I've used SwingUtilities.convertPoint() to do this. There's only one problem when the glassPane is always on, accessing the buttons and menus, I could implement mouseListeners in each of them of ofcourse. – Jesse Apr 09 '11 at 10:32
  • I got an idea in my head, but don't know if it's possible, placing the `menuBar` and the `JPanel` with the buttons on top of the `glassPane`. This way the `glassPane` can always be visible and I still have access to the controls. Since `glassPane` is always the top pane, might it be possible with a different type of `Pane` that (just like a glassPane) covers the entire `frame` and can be put between the two `JPanels` and the controls? – Jesse Apr 10 '11 at 11:46
  • @Jesse: Or simpler and better would be to just use a JLayeredPane. Oh, and if my post helped you, feel free to upvote and/or accept it. It did take a bit of time and effort to produce. – Hovercraft Full Of Eels Apr 10 '11 at 13:33
  • yes, I thought about the JLayeredPane and tried to create two JPanels next to eachother using a `BorderLayouts` CENTER and EAST and then one big JPanel on top in a higher layer. But when I try to make the top panel visible nothing happens although it is set to opaque. But you do think it should be possible to create a JPanel that covers two other JPanels using the JLayeredPane? If so, I'll try to fix my code. About the upvote, there you go! Your help is much appreciated. – Jesse Apr 10 '11 at 17:14