How can I prevent a JSlider from moving full-range? - java

I have an application where I want to use a JSlider for user input, representing a percentage of an absolute value.
Based on other variables, the minimum/maximum percentage value will be limited. I don't want to change the scale (min/max) of the JSlider itself (I want to preserve the full-range percentage scale). I created a ChangeListener that determines if the range needs to be limited, and when so, I call the JSlider.setValue() method with the appropriate (limited min/max) value. However, the JSlider itself does not reflect the value I've set. If I query the value with JSlider.getValue() it returns what I would expect (the value I set).
Is there a way to force the JSlider to only allow dragging the actual slider (thumb, knob) to a certain point, then stopping?
Update:
After spending too much time on this, I've decided that the root problem is that I'm misusing/abusing the JSlider in an unintended use model. Restricting the range of movement of the thumb to be less than the BoundedRangeModel range requires modification of the BasicSliderUI class. While the original solution proposed by aterai below does work, it requires overriding a SliderUI implementation class, impacting the portability and consistency of a plaf. So, either I have to find a different UI element or modify the JSlider BoundedRangeModel limits based on the other interdependent variable values. The downside of the latter is that the same thumb position will represent a different value based on the value of other user-editable parameters.

You might be able to override the createTrackListener(...) method of MetalSliderUI to prevent that dragging.
Edit
Another option is to use a JLayer(untested code, may take some customization to work for other LookAndFeel):
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicSliderUI;
// import javax.swing.plaf.metal.MetalSliderUI;
// import javax.swing.plaf.synth.SynthSliderUI;
// import com.sun.java.swing.plaf.windows.WindowsSliderUI;
public class DragLimitedSliderTest {
private static int MAXI = 80;
private JComponent makeUI() {
JSlider slider1 = makeSlider();
JSlider slider2 = makeSlider();
slider2.setUI(new BasicSliderUI(slider2) {
//slider2.setUI(new WindowsSliderUI(slider2) {
//slider2.setUI(new MetalSliderUI() {
//slider2.setUI(new SynthSliderUI(slider2) {
#Override protected TrackListener createTrackListener(JSlider slider) {
return new TrackListener() {
#Override public void mouseDragged(MouseEvent e) {
//case HORIZONTAL:
int halfThumbWidth = thumbRect.width / 2;
int thumbLeft = e.getX() - offset;
int maxPos = xPositionForValue(MAXI) - halfThumbWidth;
if (thumbLeft > maxPos) {
int x = maxPos + offset;
MouseEvent me = new MouseEvent(
e.getComponent(), e.getID(), e.getWhen(), e.getModifiers(),
x, e.getY(),
e.getXOnScreen(), e.getYOnScreen(),
e.getClickCount(), e.isPopupTrigger(), e.getButton());
e.consume();
super.mouseDragged(me);
} else {
super.mouseDragged(e);
}
}
};
}
});
JSlider slider3 = makeSlider();
JPanel p = new JPanel(new GridLayout(3, 1));
p.add(slider1);
p.add(slider2);
p.add(new JLayer<JSlider>(slider3, new DisableInputLayerUI()));
return p;
}
private static JSlider makeSlider() {
JSlider slider = new JSlider(0, 100, 40) {
#Override public void setValue(int n) {
super.setValue(n);
}
};
slider.setMajorTickSpacing(10);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
Dictionary dictionary = slider.getLabelTable();
if (dictionary != null) {
Enumeration elements = dictionary.elements();
while (elements.hasMoreElements()) {
JLabel label = (JLabel) elements.nextElement();
int v = Integer.parseInt(label.getText());
if (v > MAXI) {
label.setForeground(Color.RED);
}
}
}
slider.getModel().addChangeListener(new ChangeListener() {
#Override public void stateChanged(ChangeEvent e) {
BoundedRangeModel m = (BoundedRangeModel) e.getSource();
if (m.getValue() > MAXI) {
m.setValue(MAXI);
}
}
});
return slider;
}
public static void main(String... args) {
EventQueue.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// for (UIManager.LookAndFeelInfo laf: UIManager.getInstalledLookAndFeels()) {
// if ("Nimbus".equals(laf.getName())) {
// UIManager.setLookAndFeel(laf.getClassName());
// }
// }
} catch (Exception e) {
e.printStackTrace();
}
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new DragLimitedSliderTest().makeUI());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
class DisableInputLayerUI extends LayerUI<JSlider> {
#Override public void installUI(JComponent c) {
super.installUI(c);
if (c instanceof JLayer) {
JLayer jlayer = (JLayer) c;
jlayer.setLayerEventMask(
AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
}
#Override public void uninstallUI(JComponent c) {
if (c instanceof JLayer) {
JLayer jlayer = (JLayer) c;
jlayer.setLayerEventMask(0);
}
super.uninstallUI(c);
}
private Rectangle thumbRect = new Rectangle(11, 19); //magic number
private Rectangle focusRect = new Rectangle();
private Rectangle contentRect = new Rectangle();
private Rectangle trackRect = new Rectangle();
private int offset;
protected int xPositionForValue(JSlider slider, int value) {
int min = slider.getMinimum();
int max = slider.getMaximum();
int trackLength = trackRect.width;
double valueRange = (double) max - (double) min;
double pixelsPerValue = (double) trackLength / valueRange;
int trackLeft = trackRect.x;
int trackRight = trackRect.x + (trackRect.width - 1);
int xPosition;
xPosition = trackLeft;
xPosition += Math.round(pixelsPerValue * ((double) value - min));
xPosition = Math.max(trackLeft, xPosition);
xPosition = Math.min(trackRight, xPosition);
return xPosition;
}
protected int getHeightOfTallestLabel(JSlider slider) {
Dictionary dictionary = slider.getLabelTable();
int tallest = 0;
if (dictionary != null) {
Enumeration keys = dictionary.keys();
while (keys.hasMoreElements()) {
JComponent label = (JComponent) dictionary.get(keys.nextElement());
tallest = Math.max(label.getPreferredSize().height, tallest);
}
}
return tallest;
}
#Override protected void processMouseEvent(MouseEvent e, JLayer<? extends JSlider> l) {
JSlider slider = l.getView();
if (e.getID() == MouseEvent.MOUSE_PRESSED) {
//case HORIZONTAL:
//recalculateIfInsetsChanged()
Insets insetCache = slider.getInsets();
Insets focusInsets = UIManager.getInsets("Slider.focusInsets");
if (focusInsets == null) {
focusInsets = new Insets(2, 2, 2, 2); //magic number
}
//calculateFocusRect()
focusRect.x = insetCache.left;
focusRect.y = insetCache.top;
focusRect.width = slider.getWidth() - (insetCache.left + insetCache.right);
focusRect.height = slider.getHeight() - (insetCache.top + insetCache.bottom);
//calculateContentRect()
contentRect.x = focusRect.x + focusInsets.left;
contentRect.y = focusRect.y + focusInsets.top;
contentRect.width = focusRect.width - (focusInsets.left + focusInsets.right);
contentRect.height = focusRect.height - (focusInsets.top + focusInsets.bottom);
//calculateThumbSize()
Icon ti = UIManager.getIcon("Slider.horizontalThumbIcon");
if (ti != null) {
thumbRect.width = ti.getIconWidth();
thumbRect.height = ti.getIconHeight();
}
//calculateTrackBuffer()
int trackBuffer = 9; //magic number, Windows: 9, Metal: 10 ...
//calculateTrackRect()
int centerSpacing = thumbRect.height;
if (slider.getPaintTicks()) centerSpacing += 8; //magic number getTickLength();
if (slider.getPaintLabels()) centerSpacing += getHeightOfTallestLabel(slider);
trackRect.x = contentRect.x + trackBuffer;
trackRect.y = contentRect.y + (contentRect.height - centerSpacing - 1) / 2;
trackRect.width = contentRect.width - (trackBuffer * 2);
trackRect.height = thumbRect.height;
//calculateThumbLocation()
int valuePosition = xPositionForValue(slider, slider.getValue());
thumbRect.x = valuePosition - (thumbRect.width / 2);
thumbRect.y = trackRect.y;
offset = e.getX() - thumbRect.x;
}
}
#Override protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JSlider> l) {
if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
JSlider slider = l.getView();
//case HORIZONTAL:
int halfThumbWidth = thumbRect.width / 2;
int thumbLeft = e.getX() - offset;
int maxPos = xPositionForValue(slider, 80) - halfThumbWidth;
if (thumbLeft > maxPos) {
e.consume();
SliderUI ui = slider.getUI();
if (ui instanceof BasicSliderUI) {
((BasicSliderUI) ui).setThumbLocation(maxPos, thumbRect.y);
}
slider.getModel().setValue(80);
}
}
}
}

It seems there's really no way to do what I wanted in a way that will be consistent across different Look and Feel implementations. Tracing through the notifications and listeners of the JSlider, BasicSliderUI, and BoundedRangeModel it turns out that there is no way to force a recalculation of the thumb position through public methods and the base listener implementation specifically prevents this from happening when dragging or terminating the drag (mouse release). See update to original question for more.

Related

Java swing indeterminate JProgressBar starting from the left when reaching end instead of bouncing

I created a JProgressBar in a GUI application, and setted it to "indeterminate", but I don't like that it bounces instead of restarting every time it reaches the end. What can I do to fix this graphic setting?
Change the UI of the JProgressBar.
The UI is the class that paints the progress bar. The BasicProgressBarUI, makes the box bounce by default. You just have to write your own class extending MetalProgressBarUI (if you're using Metal) and overriding getBox(Rectangle), which is the method that stores the position of the box in the given rectangle.
Use JProgressBar.setUI to apply the UI to your progress bar. You can also change the defaults with UIManager.put("ProgressBarUI", "fullyQualifiedClassName") to change the default UI for a progress bar.
As Snowy_1803 has already said, you would need to override the BasicProgressBarUI#getBox(...):
import java.awt.*;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.swing.*;
import javax.swing.plaf.basic.BasicProgressBarUI;
public final class MainPanel extends JPanel implements HierarchyListener {
private transient SwingWorker<String, Void> worker;
private MainPanel() {
super(new BorderLayout());
BoundedRangeModel model = new DefaultBoundedRangeModel();
JProgressBar progressBar = new JProgressBar(model) {
#Override public void updateUI() {
super.updateUI();
setUI(new OneDirectionProgressBarUI());
}
};
List<JProgressBar> list = Arrays.asList(new JProgressBar(model), progressBar);
JPanel p = new JPanel(new GridLayout(5, 1));
list.forEach(bar -> p.add(makePanel(bar)));
JButton button = new JButton("Test start");
button.addActionListener(e -> {
if (Objects.nonNull(worker) && !worker.isDone()) {
worker.cancel(true);
}
worker = new BackgroundTask();
list.forEach(bar -> {
bar.setIndeterminate(true);
worker.addPropertyChangeListener(new ProgressListener(bar));
});
worker.execute();
});
Box box = Box.createHorizontalBox();
box.add(Box.createHorizontalGlue());
box.add(button);
box.add(Box.createHorizontalStrut(5));
addHierarchyListener(this);
add(p);
add(box, BorderLayout.SOUTH);
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
}
#Override public void hierarchyChanged(HierarchyEvent e) {
boolean isDisplayableChanged = (e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0;
if (isDisplayableChanged && !e.getComponent().isDisplayable() && Objects.nonNull(worker)) {
worker.cancel(true);
worker = null;
}
}
private static Component makePanel(Component cmp) {
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.insets = new Insets(5, 5, 5, 5);
c.weightx = 1d;
JPanel p = new JPanel(new GridBagLayout());
p.add(cmp, c);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
f.getContentPane().add(new MainPanel());
f.setSize(320, 240);
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}
class OneDirectionProgressBarUI extends BasicProgressBarUI {
#Override
protected Rectangle getBox(Rectangle r) {
Rectangle rect = super.getBox(r);
boolean vertical = progressBar.getOrientation() == JProgressBar.VERTICAL;
Insets ins = new Insets(0, 0, 0, 0); // progressBar.getInsets();
int currentFrame = getAnimationIndex();
int framecount = getFrameCount() / 2;
currentFrame = currentFrame % framecount;
// #see com/sun/java/swing/plaf/windows/WindowsProgressBarUI.java
// this code adjusts the chunk size to properly account for the
// size and gap specified in the XP style. It also does it's own
// box placement for the chunk animation. This is required because
// the inherited algorithm from BasicProgressBarUI goes back and
// forth whereas XP only goes in one direction. XP also has ghosted
// trailing chunks to create the illusion of speed. This code
// adjusts the pixel length of the animation to account for the
// trails.
if (!vertical) {
rect.y = rect.y + ins.top;
rect.height = progressBar.getHeight() - ins.top - ins.bottom;
int len = progressBar.getWidth() - ins.left - ins.right;
len += rect.width * 2; // add 2x for the trails
double delta = (double) (len) / (double) framecount;
rect.x = (int) (delta * currentFrame) + ins.left;
} else {
rect.x = rect.x + ins.left;
rect.width = progressBar.getWidth() - ins.left - ins.right;
int len = progressBar.getHeight() - ins.top - ins.bottom;
len += rect.height * 2; // add 2x for the trails
double delta = (double) (len) / (double) framecount;
rect.y = (int) (delta * currentFrame) + ins.top;
}
return rect;
}
}
class BackgroundTask extends SwingWorker<String, Void> {
#Override public String doInBackground() {
try { // dummy task
Thread.sleep(5000);
} catch (InterruptedException ex) {
return "Interrupted";
}
int current = 0;
int lengthOfTask = 100;
while (current <= lengthOfTask && !isCancelled()) {
try { // dummy task
Thread.sleep(50);
} catch (InterruptedException ex) {
return "Interrupted";
}
setProgress(100 * current / lengthOfTask);
current++;
}
return "Done";
}
}
class ProgressListener implements PropertyChangeListener {
private final JProgressBar progressBar;
protected ProgressListener(JProgressBar progressBar) {
this.progressBar = progressBar;
this.progressBar.setValue(0);
}
#Override public void propertyChange(PropertyChangeEvent e) {
String strPropertyName = e.getPropertyName();
if ("progress".equals(strPropertyName)) {
progressBar.setIndeterminate(false);
int progress = (Integer) e.getNewValue();
progressBar.setValue(progress);
}
}
}

JLabel,controlled through mosemotionliistner, returned back to original location when another jlabel(not controlled by mouse) enters and leaves JFrame

I'm making a game similar to feeding frenzy. I'm using mouseMotionListener to move a JLabel around a JFrame. At the same time, there are other JLabels that act as other fish to be eaten by the JLabel controlled by the mouse. Every time a JLabel fish (not controlled by mouse) moves in to and out of the screen, the JLabel controlled by the mouse returns back to a standard location on the screen, the center of the top half of the screen. What can I do to stop that from occurring?
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import sun.audio.*;
import java.io.*;
import java.util.Timer;
import java.util.TimerTask;
import java.awt.event.MouseEvent;
public class playFishGame extends JPanel implements MouseMotionListener {
private JFrame board;// the main board
private JLabel fish;
private JLabel enemyFishS;
private JLabel enemyFishS2;
private JLabel enemyFishS3;
private ImageIcon fishPic;
private ImageIcon enemyFishSPic;
private ImageIcon winBackground;
private ImageIcon background;
private ImageIcon loseBackground;
ImageIcon fishSmall1r = new ImageIcon("data/fishSmall1r.png");
ImageIcon fishSmall1l = new ImageIcon("data/fishSmall1l.png");
ImageIcon fishSmall2r = new ImageIcon("data/fishSmall2r.png");
ImageIcon fishSmall2l = new ImageIcon("data/fishSmall2l.png");
ImageIcon fishSmall4r = new ImageIcon("data/fishSmall4r.png");
ImageIcon fishSmall4l = new ImageIcon("data/fishSmall4l.png");
private fish fishFish;
private fish enemyFishSFish;
private fish enemyFishSFish2;
private fish enemyFishSFish3;
private int origin;
private boolean contact, win;
Cursor blankCursor = null;
public static void main(String args[]) {
playFishGame play = new playFishGame();
}
public playFishGame() {
blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
Toolkit.getDefaultToolkit().createImage("data/blank.png"),
new Point(0, 0), "blankCursor"); // blank.png is any tranparent
// image.
board = new JFrame("Play Fish Game");
board.setSize(1300, 700);
board.getContentPane().setCursor(blankCursor);
board.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
board.add(this);// adds JLabel to JFrame
this.addMouseMotionListener(this);
board.setVisible(true);
ImageIcon fishPic = new ImageIcon("data/sfr.gif");
fish = new JLabel(fishPic);
enemyFishS = new JLabel(" ");
enemyFishS2 = new JLabel(" ");
fishFish = new fish(617, 0);
enemyFishSFish = new fish(1300, 350);
enemyFishSFish2 = new fish(0, 500);
// enemyFishSFish3= new fish(1300,200);
this.add(fish);
this.add(enemyFishS);
this.add(enemyFishS2);
contact = false;
win = false;
repaint();
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if (enemyFishSFish.initiate == true) {
randomFishSmall(enemyFishSFish, enemyFishS);// picks image and
// starting
// point/side
enemyFishSFish.initiate = false;
}
if (enemyFishSFish.chooseSide == 0) {
enemyFishSFish.moveLeft();
if (enemyFishSFish.getX() < 5) {
enemyFishSFish.initiate = true;
}
} else if (enemyFishSFish.chooseSide == 1) {
enemyFishSFish.moveRight();
if (enemyFishSFish.getX() > 1295) {
enemyFishSFish.initiate = true;
}
}
if (enemyFishSFish2.initiate == true) {
randomFishSmall(enemyFishSFish2, enemyFishS2);// picks image and
// starting
// point/side
enemyFishSFish2.initiate = false;
}
if (enemyFishSFish2.chooseSide == 0) {
enemyFishSFish2.moveLeft();
if (enemyFishSFish2.getX() < 5) {
enemyFishSFish2.initiate = true;
}
} else if (enemyFishSFish2.chooseSide == 1) {
enemyFishSFish2.moveRight();
if (enemyFishSFish2.getX() > 1295) {
enemyFishSFish2.initiate = true;
}
}
enemyFishS.setLocation(enemyFishSFish.getX(), enemyFishSFish.getY());
contact(enemyFishSFish);
enemyFishS2.setLocation(enemyFishSFish2.getX(),
enemyFishSFish2.getY());
contact(enemyFishSFish2);
// contact(enemyFishSFish);
// enemyFishS2.setLocation(enemyFishSFish2.getX(),enemyFishSFish2.getY());
// enemyFishS3.setLocation(enemyFishSFish3.getX(),enemyFishSFish3.getY());
}
}, 0, 100);
board.setState(Frame.ICONIFIED);
board.setState(Frame.NORMAL);
}
public void mouseMoved(MouseEvent evt) {
System.out.println(evt.getPoint().x + ", " + evt.getPoint().y);
if ((evt.getPoint().x < 1231) && (evt.getPoint().y < 623)) {
fish.setLocation(evt.getPoint().x, evt.getPoint().y);
fishFish.override(evt.getPoint().x, evt.getPoint().y);
}
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
origin = fishFish.getX();
}
}, 0, 100);
int posneg = origin - evt.getPoint().x;
ImageIcon sfr = new ImageIcon("data/sfr.gif");
ImageIcon sfl = new ImageIcon("data/sfl.gif");
ImageIcon mfr = new ImageIcon("data/mfr.gif");
ImageIcon mfl = new ImageIcon("data/mfl.gif");
if (posneg < 0) {
if (fishFish.sFish < 10)
fish.setIcon(sfr);
if (fishFish.sFish > 9)
fish.setIcon(mfr);
}
if (posneg > 0) {
if (fishFish.sFish < 10)
fish.setIcon(sfl);
if (fishFish.sFish > 9)
fish.setIcon(mfl);
}
}
public void mouseDragged(MouseEvent evt) {
}
// 95/34
public void contact(fish enemyFish) {
if ((Math.abs(fishFish.getX() - enemyFish.getX())) < 48
&& (Math.abs(fishFish.getY() - enemyFish.getY()) < 36)
&& (fishFish.sFish < 10)) {
fishFish.sFish++;
enemyFish.initiate = true;
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
background = new ImageIcon("data/background3.png");
winBackground = new ImageIcon("data/");
loseBackground = new ImageIcon("data/");
if ((contact == false) && (win == false))
g.drawImage(background.getImage(), 0, 0, null);
if (contact == true)
g.drawImage(loseBackground.getImage(), 0, 0, null);
if (win == true)
g.drawImage(winBackground.getImage(), 0, 0, null);
}
public void randomFishSmall(fish changeFishFish, JLabel changeFish) {
int chooseType = (int) (Math.random() * 3);
// int chooseType=2;
int chooseSide = (int) (Math.random() * 2);// left=0right=1
int chooseSpeed = (int) (Math.random() * 12) + 6;
int choosePlacement = (int) (Math.random() * 1288) + 15;
changeFishFish.chooseType = chooseType;
changeFishFish.chooseSide = chooseSide;
changeFishFish.chooseSpeed = chooseSpeed;
changeFishFish.choosePlacement = choosePlacement;
if (chooseType == 0) {
if (chooseSide == 0) {
changeFish.setIcon(fishSmall1l);
changeFishFish.override(1300, choosePlacement);
// changeFishFish.
} else {
changeFish.setIcon(fishSmall1r);
changeFishFish.override(0, choosePlacement);
}
changeFish.setLocation(changeFishFish.getX(), changeFishFish.getY());
} else if (chooseType == 1) {
if (chooseSide == 0) {
changeFish.setIcon(fishSmall2l);
changeFishFish.override(1300, choosePlacement);
} else {
changeFish.setIcon(fishSmall2r);
changeFishFish.override(0, choosePlacement);
}
changeFish.setLocation(changeFishFish.getX(), changeFishFish.getY());
} else if (chooseType == 2) {
if (chooseSide == 0) {
changeFish.setIcon(fishSmall4l);
changeFishFish.override(1300, choosePlacement);
// changeFishFish.
} else {
changeFish.setIcon(fishSmall4r);
changeFishFish.override(0, choosePlacement);
}
changeFish.setLocation(changeFishFish.getX(), changeFishFish.getY());
}
}
}
First of all:
Class names start with an upper case character. Learn by example. Just look at the Java API and you will see all class names start with an upper case character.
Don't do I/O in a painting method. Painting methods are called whenever Swing determines a component needs to be repainted. It is not efficient to read files every time. Read the images when the class is created.
Don't hardcode the size of the frame - board.setSize(1300, 700); Not everybody uses the same resolution. To maximize the frame you can use: board.setExtendedState(JFrame.MAXIMIZED_BOTH);
returns back to a standard location on the screen, the center of the top half of the screen
By default a JPanel uses a FlowLayout. When you change the location of the other labels the layout manager is invoked to components a place in the position determined by the layout manager.
If you have random placement of a component because you are using the mouse to drag the component, then you need to use a "null layout" on the panel. When you do this you are now responsible for manually setting the "size and location" of every component you add to the panel.

java Swing timer to perform few tasks one after another

I am perfectly aware very similar questions few asked before. I have tried to implement solutions offered - in vain. ...The problem i am facing is to blink buttons ONE AFTER ANOTHER. I can do it for one, but when put the order of blinking in a loop - everything breaks. Any help to a new person to Java is appreciated. P.S. I am not allowed to use Threads. What i am having now is:
Timer colorButton = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < pcArray.length; i++) {
playSquare = pcArray[i];
System.out.println("PlaySquare " + playSquare);
if (playSquare == 1) {
if (alreadyColoredRed) {
colorBack.start();
colorButton.stop();
} else {
red.setBackground(Color.red);
alreadyColoredRed = true;
System.out.println("RED DONE");
}
} else if (playSquare == 2) {
if (alreadyColoredGreen) {
colorBack.start();
colorButton.stop();
} else {
green.setBackground(Color.green);
alreadyColoredGreen = true;
System.out.println("GREEN DONE");
}
}
}
}
});
Timer colorBack = new Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < pcArray.length; i++) {
playSquare = pcArray[i];
System.out.println("PlaySquare " + playSquare);
if (playSquare == 1) {
red.setBackground(Color.gray);
alreadyColoredRed = false;
System.out.println("RED PAINTED BACK");
colorBack.stop();
} else if (playSquare == 2) {
green.setBackground(Color.gray);
alreadyColoredGreen = false;
System.out.println("GREEN PAINTED BACK");
colorBack.stop();
}
}
}
});
I don't think that having two Timer instances is the way to go. The Swing Timer is notorious for 'drifting' away from the perfect beat over time.
Better to create a single timer with the logic needed to control all actions.
E.G. Showing the allowable moves for a Chess Knight.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class KnightMove {
KnightMove() {
initUI();
}
ActionListener animationListener = new ActionListener() {
int blinkingState = 0;
#Override
public void actionPerformed(ActionEvent e) {
final int i = blinkingState % 4;
chessSquares[7][1].setText("");
chessSquares[5][0].setText("");
chessSquares[5][2].setText("");
switch (i) {
case 0:
setPiece(chessSquares[5][0], WHITE + KNIGHT);
break;
case 1:
case 3:
setPiece(chessSquares[7][1], WHITE + KNIGHT);
break;
case 2:
setPiece(chessSquares[5][2], WHITE + KNIGHT);
}
blinkingState++;
}
};
public void initUI() {
if (ui != null) {
return;
}
ui = new JPanel(new GridLayout(8, 8));
ui.setBorder(new CompoundBorder(new EmptyBorder(4, 4, 4, 4),
new LineBorder(Color.BLACK,2)));
boolean black = false;
for (int r = 0; r < 8; r++) {
for (int c = 0; c < 8; c++) {
JLabel l = getColoredLabel(black);
chessSquares[r][c] = l;
ui.add(l);
black = !black;
}
black = !black;
}
for (int c = 0; c < 8; c++) {
setPiece(chessSquares[0][c], BLACK + STARTING_ROW[c]);
setPiece(chessSquares[1][c], BLACK + PAWN);
setPiece(chessSquares[6][c], WHITE + PAWN);
setPiece(chessSquares[7][c], WHITE + STARTING_ROW[c]);
}
Timer timer = new Timer(750, animationListener);
timer.start();
}
private void setPiece(JLabel l, int piece) {
l.setText("<html><body style='font-size: 60px;'>&#" + piece + ";");
}
private final JLabel getColoredLabel(boolean black) {
JLabel l = new JLabel();
l.setBorder(new LineBorder(Color.DARK_GRAY));
l.setOpaque(true);
if (black) {
l.setBackground(Color.GRAY);
} else {
l.setBackground(Color.WHITE);
}
return l;
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
KnightMove o = new KnightMove();
JFrame f = new JFrame("Knight Moves");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.setResizable(false);
f.pack();
f.setVisible(true);
}
};
SwingUtilities.invokeLater(r);
}
private JComponent ui = null;
JLabel[][] chessSquares = new JLabel[8][8];
public static final int WHITE = 9812, BLACK = 9818;
public static final int KING = 0, QUEEN = 1,
ROOK = 2, KNIGHT = 4, BISHOP = 3, PAWN = 5;
public static final int[] STARTING_ROW = {
ROOK, KNIGHT, BISHOP, KING, QUEEN, BISHOP, KNIGHT, ROOK
};
}

Swing blurred drag image

I have a simple task to implement, it works quite ok, but I am facing a very tricky issue regarding custom drag images in Swing.
The idea behind the task is just to allow the user to perform some DND between a list component and a text component, but during the drag operation to display following the mouse the exact same drawing as the renderer inside the list.
For this I use the cell renderer for the selected elements in the list and paint it over a temporary image. Then send this image to the TransferHandler and everything is fine. The problem is evident when I modify the size of the overall component and make it larger. After a certain extent, the picture that is draw no longer appears correct, but instead it has some gradient applied to it, making the content difficult to read. Following is a snippet of code that reproduces the issue:
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.DropMode;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class BasicTextListDND {
private JList<String> makeList() {
DefaultListModel<String> m = new DefaultListModel<String>();
for(int i = 0; i<10; i++) {
m.addElement("Element "+i);
}
JList<String> list = new JList<String>(m);
list.setTransferHandler(new BasicListTransferHandler());
list.setDropMode(DropMode.ON_OR_INSERT);
list.setDragEnabled(true);
list.setCellRenderer(new DefaultListCellRenderer() {
/**
* Comment for <code>serialVersionUID</code>
*/
private static final long serialVersionUID = 1L;
/** {#inheritDoc} */
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
Component listCellRendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (cellHasFocus == false && isSelected == false) {
if (index % 2 == 0) {
listCellRendererComponent.setBackground(Color.RED);
} else if (index % 3==0) {
listCellRendererComponent.setBackground(Color.GREEN);
} else {
listCellRendererComponent.setBackground(Color.BLUE);
}
}
return listCellRendererComponent;
}
});
return list;
}
private JTextArea makeTextArea() {
JTextArea textArea = new JTextArea("Drag here from JList!");
return textArea;
}
public JComponent makeUI() {
JPanel panel = new JPanel(new GridLayout(2,1));
panel.add(new JScrollPane(makeTextArea()));
panel.add(new JScrollPane(makeList()));
return panel;
}
private static void createAndShowGUI() {
JFrame f = new JFrame("BasicDnD");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BasicTextListDND app = new BasicTextListDND();
JComponent appContent = app.makeUI();
f.setContentPane(appContent);
f.setSize(600, 320);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
}
/**
*
*/
public class BasicListTransferHandler extends TransferHandler {
/**
* Comment for <code>serialVersionUID</code>
*/
private static final long serialVersionUID = 1L;
#Override
public boolean canImport(TransferHandler.TransferSupport info) {
if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
JList.DropLocation dl = (JList.DropLocation)info.getDropLocation();
if (dl.getIndex() == -1) {
return false;
}
return true;
}
#Override
public int getSourceActions(JComponent c) {
BufferedImage dragImage = makeImageFromString(c);
if (dragImage != null) {
setDragImage(dragImage);
Point mousePosition = c.getMousePosition();
if (mousePosition != null) {
setDragImageOffset(mousePosition);
}
}
return COPY;
}
private final JPanel tempDrawPanel = new JPanel();
private BufferedImage createDragImage(JList<String> list) {
int width = 0;
int height = 0;
int[] selectedIndices = list.getSelectedIndices();
for(int i =0; i<selectedIndices.length; i++){
int idx = selectedIndices[i];
Rectangle cellBounds = list.getCellBounds(idx, idx);
height += cellBounds.height;
width = Math.max(width, cellBounds.width); // we want to create a drag image as big as the largest cell
}
BufferedImage br = null;
if (width > 0 && height > 0) {
br = list.getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
return br;
}
private BufferedImage makeImageFromString(JComponent src) {
JList<String> sourceList = (JList<String>)src;
BufferedImage br = createDragImage(sourceList);
if (br != null) {
int[] selectedIndices = sourceList.getSelectedIndices();
int yD = 0;
Graphics g = br.getGraphics();
try{
for(int idx: selectedIndices) {
ListCellRenderer<? super String> cellRenderer = sourceList.getCellRenderer();
String valueAt = sourceList.getModel().getElementAt(idx);
Component c = cellRenderer.getListCellRendererComponent(sourceList, valueAt, idx, false, false);
Rectangle itemCellBounds = sourceList.getCellBounds(idx, idx);
SwingUtilities.paintComponent(g, c, tempDrawPanel, itemCellBounds.x, yD, itemCellBounds.width, itemCellBounds.height);
yD = itemCellBounds.y+itemCellBounds.height;
}
}finally {
g.dispose();
}
br.coerceData(true);
}
return br;
}
#Override
protected Transferable createTransferable(JComponent c) {
JList<String> list = (JList<String>)c;
List<String> selectedValuesList = list.getSelectedValuesList();
StringBuffer buff = new StringBuffer();
for (int i = 0; i < selectedValuesList.size(); i++) {
String val = selectedValuesList.get(i);
buff.append(val == null ? "" : val.toString());
if (i != selectedValuesList.size()- 1) {
buff.append("\n");
}
}
return new StringSelection(buff.toString());
}
}
The problem lies, I think, somewhere in the makeImageFromString method, but after 2 days of digging through Swing/AWT libraries and understanding how the drag image is drawn, I still fail to fix this issue. The bottom-line question: is there any obscure logic in AWT that applies this gradient if the drag image is over a certain size?
Any help would be greatly appreciated!
Marius.
How about translates the origin of the graphics context:
//SwingUtilities.paintComponent(g, c, tempDrawPanel, itemCellBounds.x, yD, itemCellBounds.width, itemCellBounds.height);
//yD = itemCellBounds.y+itemCellBounds.height;
SwingUtilities.paintComponent(g, c, tempDrawPanel, 0, 0, itemCellBounds.width, itemCellBounds.height);
g.translate(0, itemCellBounds.height);
Edit:
#user3619696: I misunderstood.
I would guess that the "blurred drag image" opacity depend on the Windows desktop theme. So try using translucent JWindow instead of TransferHandler#setDragImage(...).
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.image.*;
import java.util.List;
import javax.swing.*;
public class BasicTextListDND2 {
private JList<String> makeList() {
DefaultListModel<String> m = new DefaultListModel<String>();
for(int i = 0; i<10; i++) {
m.addElement("Element "+i);
}
JList<String> list = new JList<String>(m);
list.setTransferHandler(new BasicListTransferHandler());
list.setDropMode(DropMode.ON_OR_INSERT);
list.setDragEnabled(true);
list.setCellRenderer(new DefaultListCellRenderer() {
/**
* Comment for <code>serialVersionUID</code>
*/
private static final long serialVersionUID = 1L;
/** {#inheritDoc} */
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
Component listCellRendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (cellHasFocus == false && isSelected == false) {
if (index % 2 == 0) {
listCellRendererComponent.setBackground(Color.RED);
} else if (index % 3==0) {
listCellRendererComponent.setBackground(Color.GREEN);
} else {
listCellRendererComponent.setBackground(Color.BLUE);
}
}
return listCellRendererComponent;
}
});
return list;
}
private JTextArea makeTextArea() {
JTextArea textArea = new JTextArea("Drag here from JList!");
return textArea;
}
public JComponent makeUI() {
JPanel panel = new JPanel(new GridLayout(2,1));
panel.add(new JScrollPane(makeTextArea()));
panel.add(new JScrollPane(makeList()));
return panel;
}
private static void createAndShowGUI() {
JFrame f = new JFrame("BasicDnD");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BasicTextListDND2 app = new BasicTextListDND2();
JComponent appContent = app.makeUI();
f.setContentPane(appContent);
f.setSize(600, 320);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
}
/**
*
*/
class BasicListTransferHandler extends TransferHandler {
/**
* Comment for <code>serialVersionUID</code>
*/
private static final long serialVersionUID = 1L;
private final JLabel label = new JLabel() {
#Override public boolean contains(int x, int y) {
return false;
}
};
private final JWindow window = new JWindow();
public BasicListTransferHandler() {
super();
window.add(label);
//window.setBackground(new Color(0, true));
window.setOpacity(.8f);
DragSource.getDefaultDragSource().addDragSourceMotionListener(new DragSourceMotionListener() {
#Override public void dragMouseMoved(DragSourceDragEvent dsde) {
Point pt = dsde.getLocation();
pt.translate(10, 10); // offset
if (!window.isVisible()) {
window.setVisible(true);
}
window.setLocation(pt);
}
});
}
#Override protected void exportDone(JComponent c, Transferable data, int action) {
super.exportDone(c, data, action);
window.setVisible(false);
}
#Override
public boolean canImport(TransferHandler.TransferSupport info) {
if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return false;
}
JList.DropLocation dl = (JList.DropLocation)info.getDropLocation();
if (dl.getIndex() == -1) {
return false;
}
return true;
}
#Override
public int getSourceActions(JComponent c) {
BufferedImage dragImage = makeImageFromString(c);
if (dragImage != null) {
//setDragImage(dragImage);
//Point mousePosition = c.getMousePosition();
//if (mousePosition != null) {
// setDragImageOffset(mousePosition);
//}
label.setIcon(new ImageIcon(dragImage));
window.setLocation(-2000, -2000);
window.pack();
}
return COPY;
}
private final JPanel tempDrawPanel = new JPanel();
private BufferedImage createDragImage(JList<String> list) {
int width = 0;
int height = 0;
int[] selectedIndices = list.getSelectedIndices();
for(int i =0; i<selectedIndices.length; i++){
int idx = selectedIndices[i];
Rectangle cellBounds = list.getCellBounds(idx, idx);
height += cellBounds.height;
width = Math.max(width, cellBounds.width); // we want to create a drag image as big as the largest cell
}
BufferedImage br = null;
if (width > 0 && height > 0) {
br = list.getGraphicsConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
return br;
}
private BufferedImage makeImageFromString(JComponent src) {
JList<String> sourceList = (JList<String>)src;
BufferedImage br = createDragImage(sourceList);
if (br != null) {
int[] selectedIndices = sourceList.getSelectedIndices();
int yD = 0;
Graphics g = br.getGraphics();
try{
for(int idx: selectedIndices) {
ListCellRenderer<? super String> cellRenderer = sourceList.getCellRenderer();
String valueAt = sourceList.getModel().getElementAt(idx);
Component c = cellRenderer.getListCellRendererComponent(sourceList, valueAt, idx, false, false);
Rectangle itemCellBounds = sourceList.getCellBounds(idx, idx);
//SwingUtilities.paintComponent(g, c, tempDrawPanel, itemCellBounds.x, itemCellBounds.y + yD, itemCellBounds.width, itemCellBounds.height);
//yD = itemCellBounds.y+itemCellBounds.height;
SwingUtilities.paintComponent(g, c, tempDrawPanel, 0, 0, itemCellBounds.width, itemCellBounds.height);
g.translate(0, itemCellBounds.height);
}
}finally {
g.dispose();
}
br.coerceData(true);
}
return br;
}
#Override
protected Transferable createTransferable(JComponent c) {
JList<String> list = (JList<String>)c;
List<String> selectedValuesList = list.getSelectedValuesList();
StringBuffer buff = new StringBuffer();
for (int i = 0; i < selectedValuesList.size(); i++) {
String val = selectedValuesList.get(i);
buff.append(val == null ? "" : val.toString());
if (i != selectedValuesList.size()- 1) {
buff.append("\n");
}
}
return new StringSelection(buff.toString());
}
}

Mouse Listener Not Registering for JScrollPane within JLayeredPane

I am working on a project involving JSwing.
The project consists of a JLayeredPane that contains a JScrollPane and a game board (JPanel with JPanels with JLabel).
My problem is that I added a custom mouse listener to the JLayeredPane. However, when I click on the JScrollPane area the listener doesn't seem to register. However, if I click other places, it does.
Can anyone explain why this is occuring?
Thanks in advance!
Code to create panels + add listener to JLayeredPane:
private void initPanels() {
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.setLayout(new BorderLayout());
layeredPane.add(makeBoardPanel(), BorderLayout.CENTER, JLayeredPane.DEFAULT_LAYER);
JPanel panel = new JPanel(new BorderLayout());
panel.add(makeButtonPanel(), BorderLayout.WEST);
panel.add(makeDeckPanel(), BorderLayout.EAST);
layeredPane.add(panel, BorderLayout.SOUTH, JLayeredPane.DEFAULT_LAYER);
layeredPane.add(makeMeeplePanel(), BorderLayout.NORTH, JLayeredPane.DEFAULT_LAYER);
//TODO: fix adapter + add others;
TileMouseAdapter tileMouseAdapter = new TileMouseAdapter(layeredPane);
layeredPane.addMouseListener(tileMouseAdapter);
layeredPane.addMouseMotionListener(tileMouseAdapter);
add(layeredPane);
myController.reset(TileDeck.getDemo(), NUM_PLAYERS); //sets up game
}
Code to make Board with JScrollPane:
private JScrollPane makeBoardPanel() {
JScrollPane scrollPane = new JScrollPane(CarcassoneBoard.getInstance());
CarcassoneBoard.getInstance().setScrollPane(scrollPane);
return scrollPane;
}
Here is the code for that last line (myController.reset( ... ))
public void reset(Map<TileFactory, Integer> deck, int numPlayers) {
myDeck.reset(deck);
myBoard.reset(myDeck.getDeckSize()); //only panel contained in JScrollPane
myMeeplePanel.reset(numPlayers);
}
Reset method for myBoard ...
public void reset(int num_tiles) {
super.removeAll();
NUM_TILES = num_tiles;
BOARD_SIZE = (int) Math.ceil(num_tiles / 2.0);
setLayout(new GridLayout(BOARD_SIZE, BOARD_SIZE, 1, 1));
setPreferredSize(new Dimension(BOARD_SIZE * TILE_WIDTH, BOARD_SIZE * TILE_HEIGHT));
STARTING_TILE = new BubbleCity((int) Math.ceil(BOARD_SIZE / 2.0 - 1), (int) Math.ceil(BOARD_SIZE / 2.0 - 1));
myTiles = new TilePanel[BOARD_SIZE][BOARD_SIZE];
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
myTiles[i][j] = new TilePanel(i, j, new EmptyTile(i, j));
add(myTiles[i][j]);
}
}
myTiles[STARTING_TILE.getRow()][STARTING_TILE.getCol()].setTile(STARTING_TILE);
revalidate();
repaint();
}
Here is the code for the mouse listener:
public class TileMouseAdapter extends MouseAdapter {
private JLayeredPane myLayeredPane;
private Tile myTile;
private TilePanel myClickedPanel;
public TileMouseAdapter(JLayeredPane layeredPane) {
myLayeredPane = layeredPane;
}
private void reset() {
if (myTile != null) {
myLayeredPane.remove(myTile);
myLayeredPane.revalidate();
myLayeredPane.repaint();
}
myTile = null;
myClickedPanel = null;
}
//point p is a point relative to myLayeredPane...
public Point translatePoint(Component component, Point p) {
return SwingUtilities.convertPoint(myLayeredPane, p, component);
}
//point p is relative to the screen (event.getLocationOnScree()). We want to check if that point is in component.
public boolean containsPoint(Component component, Point p) {
Point translated = translatePoint(component, p );
return translated.getX() > 0 && translated.getY() > 0
&& translated.getX() < component.getWidth() && translated.getY() < component.getHeight();
}
#Override
public void mouseClicked(MouseEvent event) {
}
#Override
public void mousePressed(MouseEvent event) {
//no registering jscrollpane?
CarcassoneBoard board = CarcassoneBoard.getInstance();
TileDeck deck = TileDeck.getInstance();
System.out.println("pressed!");
if (containsPoint(deck, event.getPoint())) {
System.out.println("deck");
myClickedPanel = deck;
myTile = myClickedPanel.getTile();
}
else if (containsPoint(board.getScrollPane().getViewport(), event.getPoint())) {
System.out.println("board");
Component component = board.getComponentAt(translatePoint(board.getScrollPane().getViewport(), event.getPoint()));
if (component instanceof CarcassoneBoard) {return;} //user clicked in between tiles...do nothing.
myClickedPanel = (TilePanel) component;
myTile = myClickedPanel.getTile();
}
if (myTile == null || myTile instanceof EmptyTile || !myTile.isDraggable()) {
//TODO: scroll with drag!
reset();
return;
}
myClickedPanel.setEmpty(); //panel will set an empty tile automatically and remove myTile...
int x = event.getPoint().x - myTile.getWidth() / 2;
int y = event.getPoint().y - myTile.getHeight() / 2;
myTile.setLocation(x, y);
myLayeredPane.revalidate();
myLayeredPane.repaint();
try {
myLayeredPane.add(myTile, JLayeredPane.DRAG_LAYER);
myLayeredPane.revalidate();
myLayeredPane.repaint();
} catch (IllegalArgumentException e) {
//TODO: deal with this?
//gives error for some unknown reason, but doesnt effect anything? ignore...dumb error cus jswing sucks
}
}
#Override
public void mouseDragged(MouseEvent event) {
if (myTile == null) {
return;
}
int x = event.getPoint().x - myTile.getWidth() / 2;
int y = event.getPoint().y - myTile.getHeight() / 2;
myTile.setLocation(x, y);
myLayeredPane.revalidate();
myLayeredPane.repaint();
}
#Override
public void mouseReleased(MouseEvent event) {
if (myTile == null) {// || !myTile.isDraggable()) {
return; //do nothing...board scrolling
}
CarcassoneBoard board = CarcassoneBoard.getInstance();
myLayeredPane.remove(myTile);
TilePanel dropped = (TilePanel) board.getComponentAt(translatePoint(board.getScrollPane().getViewport(), event.getPoint()));
if (dropped == null) {
//reset tile to original spot
myClickedPanel.setTile(myTile);
reset();
return;
}
dropped.setTile(myTile);
reset();
}
}
NOTE:
I changed makeBoardPanel() to this:
private JPanel makeBoardPanel() {
return CarcassoneBoard.getInstance();
}
and the listener seemed to register clicks on the board which is what led me to believe
that it has something to do with the JScrollPane. I determined this by putting a print statement right after the mousePressed method header. When I clicked on the board it printed the statement right before complaining about the fact that I was referencing a JScrollPane that didn't exist later in the code :/

Categories