Pick up and drop with image moving with the mouse

I

Icarus

I am currently working on creating a game platform where one can pick
up tokens of various shapes and place them on a game board. For
precise placement, I need an image of these tokens in their original
size to be moving around with the mouse cursor until the token is
placed on the board.

My initial approach was to convert the token into an original-sized
picture and setting this picture as mouse cursor. However, my system
determines a maximum size for custom cursors which is far too small
for my needs.

Now I am at a loss. My idea now is to draw the picture on the
GlassPane of my current JFrame and move it according to
MouseMotionEvents. But that seems to be the most performance-heavy
solution there is.
I have found an example on http://weblogs.java.net/blog/gfx/archive/2005/10/drag_and_drop_e.html,
but it comes with no direct explanation of the steps involved and is
published under a license I don't want to have in my program. However,
without understanding the steps involved, I can only copy the code,
thus violating the license agreements.

So I want to ask you:
- What would be the most effective way of (visibly) moving objects
around with the mouse cursor on the GlassPane?
- Or: Can you think of any other way how this could be accomplished?

I would be really grateful for any pointers in the right direction. As
this problem creeped up right at the end of my project, my timetable
is messed, I have 3 weeks for solving the problem, testing it and
fixing up the documentation, I am quite on edge at the moment.
 
I

Icarus

Forgot to mention: the actual data of the token is stored and
processed elsewhere, so I don't have to deal with transfering actual
data from one component to the next. The functionality I need is
solely for presenting the changes to the user.
 
D

Daniele Futtorovic

I am currently working on creating a game platform where one can pick
up tokens of various shapes and place them on a game board. For
precise placement, I need an image of these tokens in their original
size to be moving around with the mouse cursor until the token is
placed on the board.

My initial approach was to convert the token into an original-sized
picture and setting this picture as mouse cursor. However, my system
determines a maximum size for custom cursors which is far too small
for my needs.

Now I am at a loss. My idea now is to draw the picture on the
GlassPane of my current JFrame and move it according to
MouseMotionEvents.

Don't think using the GlassPane for this is the best idea. It should be
reserved for other uses, IMHO.
But that seems to be the most performance-heavy
solution there is.

Don't know about that.


Here's a way to do it. As you can see, I've used the ContentPane instead
of the GlassPane.

<code>
package scratch;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.imageio.ImageIO;

/**
*
* @author da.futt
*/
public class DragPaneTest {

private static Icon createIcon() throws Exception {
return new ImageIcon(
ImageIO.read(
DragPaneTest.class.getResourceAsStream("withAMelon.png")
)
);
}

public static void main(String[] s){
EventQueue.invokeLater(new Runnable(){
public void run(){
try {
run0();
}
catch (Exception x){
x.printStackTrace();
}
}

private void run0() throws Exception {

JFrame f = new JFrame("DragPaneTest");

final DraggingPane cgp = new DraggingPane();

cgp.setIcon( createIcon() );

f.setContentPane(cgp);

ControllerInterface ci = new ControllerInterface(){
public void setIconLocation(Point p) {
cgp.setIconLocation(p);
}

public void setIconVisible(boolean b) {
cgp.setIconShowing(b);
}

public boolean getIconVisible() {
return cgp.getIconShowing();
}
};

MouseTracker mt = new MouseTracker(ci);
cgp.addMouseListener( mt );
cgp.addMouseMotionListener( mt );

f.setSize(new Dimension(500, 400));
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

f.setLocationRelativeTo(null);

f.setVisible(true);
}
});

}



private static interface ControllerInterface {

void setIconLocation(Point p);

void setIconVisible(boolean b);

boolean getIconVisible();
}

private static class MouseTracker
extends MouseAdapter
{
private final
ControllerInterface controller
;

private boolean
showing = false
;

public MouseTracker(ControllerInterface ci){
controller = ci;
}

@Override
public void mouseClicked(MouseEvent e) {
showing = ! showing;
controller.setIconVisible(showing);
}

@Override
public void mouseMoved(MouseEvent e) {
controller.setIconLocation(e.getPoint());
}
}

private static class DraggingPane
extends JPanel
{
private Icon
currentIcon
;

private Rectangle
iconBounds
;

private boolean
showIcon = false
;

public DraggingPane(){
setOpaque(true);
}

protected Rectangle getCurrentIconBounds(){
return iconBounds;
}

protected void setCurrentIconBounds(Rectangle r){
iconBounds = r;
}

public Icon getIcon(){
return currentIcon;
}

public boolean getIconShowing(){
return showIcon;
}

public void setIcon(Icon i){
if( i == null )
throw new NullPointerException();

currentIcon = i;

Rectangle r = getCurrentIconBounds();

if( getIcon() != null && getIconShowing() ){

RepaintManager.currentManager(this).addDirtyRegion(this, r.x, r.y,
r.width, r.height);
}

Point p = r == null ?
new Point( getWidth() >> 1, getHeight() >> 1 )
: r.getLocation();

r = new Rectangle( p, new Dimension(i.getIconWidth(),
i.getIconHeight()) );
setCurrentIconBounds( r );

if( getIconShowing() ){

RepaintManager.currentManager(this).addDirtyRegion(this, r.x, r.y,
r.width, r.height);
}
}

public void setIconShowing(boolean b){
if( ! (b ^ showIcon) ){
return ;
}

showIcon = b;

if( getIcon() != null ){
Rectangle r = getCurrentIconBounds();

RepaintManager.currentManager(this).addDirtyRegion(this, r.x, r.y,
r.width, r.height);
}
}

public void setIconLocation(Point p){
if( p.equals( getCurrentIconBounds().getLocation() ) ){
return ;
}

Rectangle r = getCurrentIconBounds();

if( getIconShowing() ){

RepaintManager.currentManager(this).addDirtyRegion(this, r.x, r.y,
r.width, r.height);
}

r.setLocation(p);

if( getIconShowing() ){

RepaintManager.currentManager(this).addDirtyRegion(this, r.x, r.y,
r.width, r.height);
}
}

@Override
public void paint(Graphics g){
super.paint(g);

if( getIconShowing() && getIcon() != null ){
Point p = getCurrentIconBounds().getLocation();
getIcon().paintIcon(this, g, p.x, p.y);
}
}
}
}
</code>
 
I

Icarus

Here's a way to do it. As you can see, I've used the ContentPane instead
of the GlassPane.

I am using the ContentPane for my content already, i. e. the game
board which must remain visible and able to react to MouseEvents
itself, or else the placement won't work. Hence why I can't substitute
the ContentPane in my program.

It might be possible to merge this code with my own, but wouldn't that
mean that the content below the icon won't react to MouseEvents? And
as I am already using the ContentPane with a LayoutManager, is it such
a good idea to paint over it, especially through overwriting the
paint()-method?
 
I

Icarus

There might even be a quite different approach in creating a JLabel
from the image, putting it on the GlassPane and moving it around via
setLocation(). That way, I don't even have to bother with overriding
the paint methods. Putting it directly on the contentPane creates
weird-looking results thanks to the LayoutManagers. This now has the
problem that the GlassPane somehow catches the MouseEvents the other
Components need, even if it's disabled.

I have prepared a sample code replicating the problem. The goal is
that the listener registered on the JPanel fires, i. e. displays the
line "Received mouse event!".

Please keep in mind that my original program consists of multiple
JPanels, each containing other panels. So if I have to redirect the
event manually to each component would result in a truckload of code
that's very hard to service.


import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.WindowConstants;


public class PickUpAndDrop extends JFrame{

// Use Singleton design pattern to provide easy access of the
relevant object
// to the listeners
private static PickUpAndDrop instance = new PickUpAndDrop();

// The label to be moved around
JLabel label;

private PickUpAndDrop(){
this.setSize(200, 200);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

// Create a panel on the ContentPane that will react to user input
JPanel listenerPanel = new JPanel();
listenerPanel.addMouseListener(this.createListener());
this.add(listenerPanel);

// Create a JLabel to be moved around on the GlassPane
java.net.URL url = this.getClass().getResource("image.png");
ImageIcon image = new ImageIcon(url);
this.label = new JLabel(image);

// Get the GlassPane and prepare it for display
JPanel glasspane = (JPanel)this.getGlassPane();
glasspane.setVisible(true);
glasspane.setEnabled(false);
glasspane.setOpaque(false);
glasspane.addMouseMotionListener(this.createMotionListener());

// Put the label on the GlassPane
glasspane.add(this.label);
}

// Move the label around
protected void moveLabel(Point position){
this.label.setLocation(position);
}

// Create a listener for giving out a printline
// once the listener receives an event
private MouseAdapter createListener(){

MouseAdapter listener = new MouseAdapter(){

public void mouseClicked(MouseEvent event) {
System.out.println("Received mouse event!");
}
};

return listener;
}

// Create a listener that orders the label to be moved
// to the position of the cursor
private MouseMotionAdapter createMotionListener(){

MouseMotionAdapter listener = new MouseMotionAdapter(){

public void mouseMoved(MouseEvent event) {

Point aktPosition = event.getPoint();
PickUpAndDrop.instance.moveLabel(aktPosition);
}
};

return listener;
}

public static void main(String[] args){
PickUpAndDrop frame = PickUpAndDrop.instance;
frame.setVisible(true);
}
}
 
D

Daniele Futtorovic

I am using the ContentPane for my content already, i. e. the game
board which must remain visible and able to react to MouseEvents
itself, or else the placement won't work. Hence why I can't substitute
the ContentPane in my program.

That's an illicit conclusion. Nothing in the code I wrote prevents mouse
events from being fired. Neither is there anything keeping you from
replacing the content pane in the beginning of UI building with one like
the one I wrote and populating it just as you did before.
It might be possible to merge this code with my own, but wouldn't that
mean that the content below the icon won't react to MouseEvents?

No, it does *not* mean that. On the contrary, you will as a matter of
fact have trouble *getting* the mouse event if there's a child Component
of the content pane below the cursor. So it would be better to use the
GlassPane, after all (see below).
And
as I am already using the ContentPane with a LayoutManager, is it such
a good idea to paint over it, especially through overwriting the
paint()-method?

No problem whatsoever /overriding/ it -- as long as super.paint is
called beforehand.

-.-

I merely thought it would be better not to use the GlassPane out of
gusto. Plus there's the problem that you would have to set the
replacement GlassPane *visible* (for PaintEvents to be honoured), which
it normally isn't.
But given the issue of MouseEvents, it may be better to use the
GlassPane. You'd set it visible at the beginning of the drag and
invisible at the end of it.
Otherwise, you'd have to override the content pane's processMouseEvent
method to intercept the relevant events (which would otherwise be
delegated to child Components). That's not all that difficult, but a tid
bit more so.

Furthermore, let me note that there are a few bugs and glitches, and
some uncomelinesses, in the code I wrote. It shouldn't be simply
incorporated as is. It was meant to show the idea of how to do it. Which
I think it does.
 
D

Daniele Futtorovic

There might even be a quite different approach in creating a JLabel
from the image, putting it on the GlassPane and moving it around via
setLocation().

I think that's a bad idea. It will surely result in more processor work,
apart from being rather inappropriate design, IMO.

That way, I don't even have to bother with overriding
the paint methods. > Putting it directly on the contentPane creates
weird-looking results thanks to the LayoutManagers.

Overriding the paint method isn't a bother. It's a basic accessory in
advanced UI code design.

Your code should work *perfectly*, LayoutManagers, LayoutManager2s
notwithstanding, if you do it properly.
This now has the
problem that the GlassPane somehow catches the MouseEvents the other
Components need, even if it's disabled.

Yes, if the GlassPase is visible, it will get the MouseEvent, and the
Components below won't. That's the point of the GlassPane. It's not
disabled if it's *visible*.

But wouldn't you want the Components below *not* to receive events while
the dragging occurs? Seems to me this would be beneficial. Just remember
to hide the GlassPane when you're done ( setVisible(false) ).
 
I

Icarus

I think that's a bad idea. It will surely result in more processor work,
apart from being rather inappropriate design, IMO.

The problem I faced using any other approach was that the drawn image
on the GlassPane isn't properly deleted when the image is moved. I
tried quite a lot here - using clearRect from Graphics2D, draw an
invisible image on top of it, trying to repaint the old area etc.
Using setLocation(), on the other hand, clears the old location
automatically.
But wouldn't you want the Components below *not* to receive events while
the dragging occurs? Seems to me this would be beneficial.

Actually, the components below need to receive an event in order to
properly calculate the Panel in which to place the component. This is
nothing I can change anymore at this point.

I managed to let the events through using an AWTEventListener for
getting the image moved on the GlassPane. And using the label
approach, it is mostly working at the moment. But if I go back to the
approach using paint: how do I delete the old image from view?
 
D

Daniele Futtorovic

The problem I faced using any other approach was that the drawn image
on the GlassPane isn't properly deleted when the image is moved. I
tried quite a lot here - using clearRect from Graphics2D, draw an
invisible image on top of it, trying to repaint the old area etc.
Using setLocation(), on the other hand, clears the old location
automatically.


Actually, the components below need to receive an event in order to
properly calculate the Panel in which to place the component. This is
nothing I can change anymore at this point.

I managed to let the events through using an AWTEventListener for
getting the image moved on the GlassPane. And using the label
approach, it is mostly working at the moment.

Glad you managed to find a working solution.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,906
Latest member
SkinfixSkintag

Latest Threads

Top