2

I'm working on a GUI for a card game and am using the ACM's student graphics library for the sake of familiarity. I have written a program that draws my solitaire game to the screen, and am having trouble making it interactive.

Background:

There are a lot of classes here, and I'll do my best to describe them each.

  • Top level JFrame containing the application.
    • GCanvas (that holds all the graphics objects)
      • SolitaireGameControl (GCompound holding all the other GCompounds making up the solitaire game)
        • Array of PileViews, a pile of cards (GCompound consisting of an array of Cards)
          • Cards (GCompound consisting of rectangles and labels)

(GCompound: a collection of graphics objects treated as one object. (If car was a GCompound, it would have GOval[] wheels, GRect body and so when I add it to the canvas, it displays as one object))

A card as seen from the top-level class would look like a bit like this: jFrame.gCanvas.solitaireGameControl.pileViews[pile number].cardView

What I've been trying to do is add a MouseListener to every single card, so that when a card is clicked and a MouseEvent is fired, MouseEvent e.getSource() = the card that was clicked.

Here's how it looks now:

public SolitaireGameControl(SolitaireGame game) {
    this.game = game; // Model of the game.
    this.pileViews = PileView.getPileViews(game.drawPiles); // ArrayList of PileViews (the pile of cards)

    for(PileView pv : pileViews) {
        for(CardView cv : pv.cardViews) {
            cv.addMouseListener(this); // add a mouseListener to the card
        }
    }

    this.addMouseListener(this); // if I don't include this, nothing happens when I click anything. If I do include this, this whole object is the source.
}

@Override
public void mouseClicked(MouseEvent e) {
    System.out.println(e.getSource()); // should return the card I clicked.
}

Picture of the problem

When I run this program, the source of every event is SolitaireGameControl, granted I leave in the this.addMouseListener(this);. If I take out this statement, nothing is printed at all, leading me to believe that the mouseListeners I have added are only working one level deep. (The first GCompound on the canvas, not the GCompounds inside it.)

Therefore, my question is as follows: Is there a way to get a MouseListener for a GCompound inside of a GCompound inside of a GCompound, and have MouseEvent's getSource to correctly identify the card? If not, is there a way to restructure my program to make it work as intended? (I know I should really be using a better graphics library for starters.)

Zach Hall
  • 209
  • 3
  • 12
  • 1
    Please have a look at my code [here](http://stackoverflow.com/questions/9692465/java-how-do-i-drag-and-drop-a-control-to-a-new-location-instead-of-its-data/9692683#9692683) for an example of dragging playing cards around a table. – Hovercraft Full Of Eels Apr 01 '12 at 21:28
  • Interesting! So, what you did was create JLabels with an image, set them onto a JLayeredPane, and put a mouseListener on each? – Zach Hall Apr 01 '12 at 21:36
  • Also, `Component comp = basePane.getComponentAt(mEvt.getPoint());` seems to be the type of code I'm looking for that would solve my problem... if there are multiple components at the same point, how does it figure out which one to select? Is it selecting the card from the topmost layer? – Zach Hall Apr 01 '12 at 21:43

1 Answers1

1

That would make sense. From my experience, if I put some components inside a top-level container, the container is the one that receives input events.

Have you tried an approach where you do something like:

/* This is the mouse listener for the top-level container. */
@Override
public void mouseClicked(MouseEvent e) {
    for(PileView pv : pileViews) {
        for(CardView cv : pv.cardViews) {
            if(cv.getBounds().contains(e.getPoint())) {
                cv.dispatchEvent(e);
            }
        }
    }
}

... and then handle mouse clicks on a 'CardView' level normally.

When the top-level container receives a mouse event, it checks if the mouse interacted with a card based on the location of the event (if the card's area contains the point). If it did, it passes down the mouse event to the card's mouse listener.

I'm assuming that the elements near the beginning of 'pv.cardViews' are the cards that are more to the front.

Martin Tuskevicius
  • 2,590
  • 4
  • 29
  • 46
  • Very helpful! I'm trying it out right now, but I'm not sure if getBounds is returning anything useful or not. I'm watching everything go by in the debugger and am getting rectangles for each getBounds with outputs like so: `"cv.getBounds()"= GRectangle (id=119) myHeight= 141.0 myWidth= 81.0 xc= 0.0 yc= 15.0` Every time I click on a card the check fails. – Zach Hall Apr 01 '12 at 22:02
  • http://pastebin.com/JZQ5Zxj4 - for a better explanation, here's the output. My point is the mouse pointer's coordinates. Each of the lines below is the card on the field's getbounds() output. The coordinates all seem to be 0.0 for X, perhaps relative to their parents, the PileViews, which add them at (0,0 0,15 0,30, etc) – Zach Hall Apr 01 '12 at 22:07
  • Ah-ha! I think I've got it! `PileView pv = (PileView) this.getElementAt(p); CardView cv = (CardView) pv.getElementAt(pv.getLocalPoint(p));` - the problem was that in every GObject, there is a new coordinate grid created at (0,0) so that everything can be relative to the object. After modifying your code a bit I figured out how to change those local coordinates into ones on the entire GCanvas. It appears to work now. Thanks! – Zach Hall Apr 01 '12 at 22:17
  • You are right. Component.getBounds() is relative to the component, meaning (0, 0) is the corner of the component NOT the container. The MouseEvent.getPoint() on the other hand is relative to the container. So (0, 0) on the component may be (100, 100) on the container. Also, if this fixed your problem, mark this as the answer ;) – Martin Tuskevicius Apr 01 '12 at 22:21