Been working with JTabbedPane and trying to customize it when using SCROLL_TAB_LAYOUT specifically with the scroll direction buttons.
I'm extending BasicTabbedPaneUI, but I don't see a method or ability to change the location of the scroll buttons. Searched around and don't see any one doing this other than just using different look and feels. Control over the location of scroll direction buttons would be very useful in general I feel.
Does anyone have any ideas how to do this while extending BasicTabbedPaneUI or any other method?
I'm assuming you want to move the scroll backwards button to the other side of the tabs.
In the BasicTabbedPaneUI class, there's a createDecreaseButton method that's package only (no access modifier).
It appears you're going to have to create your own BasicTabbedPaneUI class, with your own version of createDecreaseButton.
I changed the scroll button position without using many other components but simply extends the BasicTabbedPaneUI and hacking its paint() method to adjust the button position.
Here is the code:
public class MyTabbedScrollPane extends JTabbedPane {
public MyTabbedScrollPane ()
{
super ();
}
public MyTabbedScrollPane ( final int tabPlacement )
{
super ( tabPlacement );
}
public MyTabbedScrollPane ( final int tabPlacement, final int tabLayoutPolicy )
{
super ( tabPlacement, tabLayoutPolicy );
initialize();
}
public void initialize() {
setUI(new MyTabbedPaneUI());
}
private class MyTabbedPaneUI extends BasicTabbedPaneUI {
private int leadingTabIndex;
private Point tabViewPosition;
private Component adjustedButton;
private boolean scrollableTabLayoutEnabled() {
return tabPane.getTabLayoutPolicy() == SCROLL_TAB_LAYOUT;
}
/*
* Target button and view port utilities
*/
private Component findBackwardButton() {
Component[] comps = tabPane.getComponents();
for(Component comp:comps) {
if(comp instanceof BasicArrowButton) {
int direction = ((BasicArrowButton)comp).getDirection();
if(tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM) {
if(direction == WEST) {
return comp;
}
}
}
}
return null;
}
private JViewport findViewPort() {
Component[] comps = tabPane.getComponents();
for(Component comp:comps) {
if(comp instanceof JViewport) {
return (JViewport)comp;
}
}
return null;
}
/*
* Override View port controlling (copy from BasicTabbedPaneUI.java)
*/
public void scrollForward(int tabPlacement) {
JViewport viewport = findViewPort();
Dimension viewSize = viewport.getViewSize();
Rectangle viewRect = viewport.getViewRect();
if (tabPlacement == TOP || tabPlacement == BOTTOM) {
if (viewRect.width >= viewSize.width - viewRect.x) {
return; // no room left to scroll
}
} else { // tabPlacement == LEFT || tabPlacement == RIGHT
if (viewRect.height >= viewSize.height - viewRect.y) {
return;
}
}
setLeadingTabIndex(tabPlacement, leadingTabIndex+1);
}
public void scrollBackward(int tabPlacement) {
if (leadingTabIndex == 0) {
return; // no room left to scroll
}
setLeadingTabIndex(tabPlacement, leadingTabIndex-1);
}
public void setLeadingTabIndex(int tabPlacement, int index) {
JViewport viewport = findViewPort();
leadingTabIndex = index;
Dimension viewSize = viewport.getViewSize();
Rectangle viewRect = viewport.getViewRect();
int offsetX = adjustedButton.getWidth()+2;
switch(tabPlacement) {
case TOP:
case BOTTOM:
tabViewPosition.x = leadingTabIndex == 0? 0-offsetX : rects[leadingTabIndex].x-offsetX;
if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
// We've scrolled to the end, so adjust the viewport size
// to ensure the view position remains aligned on a tab boundary
Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x,
viewRect.height);
viewport.setExtentSize(extentSize);
}
break;
case LEFT:
case RIGHT:
tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y;
if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
// We've scrolled to the end, so adjust the viewport size
// to ensure the view position remains aligned on a tab boundary
Dimension extentSize = new Dimension(viewRect.width,
viewSize.height - tabViewPosition.y);
viewport.setExtentSize(extentSize);
}
}
viewport.setViewPosition(tabViewPosition);
}
/*
* UI Rendering
*/
public void paint(final Graphics g, JComponent c) {
super.paint(g, c);
if(scrollableTabLayoutEnabled()) {
if(adjustedButton == null) {
adjustedButton = findBackwardButton();
tabViewPosition = new Point(0-(adjustedButton.getWidth()+2), 0);
Component[] comps = tabPane.getComponents();
for(Component comp:comps) {
if(comp instanceof BasicArrowButton) {
if(comp instanceof BasicArrowButton) {
BasicArrowButton button = (BasicArrowButton)comp;
int direction = button.getDirection();
if(tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM) {
// left align the west button
if(direction == WEST) {
button.removeActionListener(button.getActionListeners()[0]);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
scrollBackward(tabPane.getTabPlacement());
}
});
} else if(direction == EAST) {
button.removeActionListener(button.getActionListeners()[0]);
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
scrollForward(tabPane.getTabPlacement());
}
});
}
}
}
}
}
}
if(adjustedButton != null && adjustedButton.isVisible()) {
// move the scroll button
int by = adjustedButton.getY();
adjustedButton.setLocation(0, by);
findViewPort().setViewPosition(tabViewPosition);
return;
}
}
}
}
}
Related
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.
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 :/
I wrote a simple implementation of the Game of life with java applets.
Here's is the source code for the Applet and the Model.
When I click the button to get the next iteration these Exceptions get thrown.
Z:\GameOfLife>appletviewer driver.html
Exception in thread "AWT-EventQueue-1" java.lang.ArrayIndexOutOfBoundsException:
65
at GameOfLifeApplet.mouseClicked(GameOfLifeApplet.java:63)
at java.awt.Component.processMouseEvent(Component.java:6219)
at java.awt.Component.processEvent(Component.java:5981)
at java.awt.Container.processEvent(Container.java:2041)
at java.awt.Component.dispatchEventImpl(Component.java:4583)
at java.awt.Container.dispatchEventImpl(Container.java:2099)
at java.awt.Component.dispatchEvent(Component.java:4413)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:599)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThre ad.java:269)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:184)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThre ad.java:174)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:169)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Try this code that adds the button once rather than every call to paint(). Note that this source still throws AIOOBE if you click outside the grid (and not on the button), but that seems like a basic logic error you should investigate once the button is fixed.
// <applet code='GameOfLifeApplet' width=580 height=650></applet>
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class GameOfLifeApplet extends Applet implements MouseListener,ActionListener
{
//the x and y coordinates to get the location of the clicked points
private int xCo, yCo;
private int diff_x, diff_y;
private GameOfLife game = new GameOfLife();
private Button nextButton = null;
public void init()
{
setLayout(null);
nextButton = new Button("Next Stage");
diff_x = diff_y = 600 / game.getGridSize();
nextButton.setLocation(250, 575);
nextButton.setSize(120, 30);
// add the button once only!
add(nextButton);
addMouseListener(this);
}
private void drawEmptyGrid(Graphics g)
{
g.setColor(Color.white);
g.fillRect(0,0,600,600);
g.setColor(Color.black);
for(int i=0;i<game.getGridSize();++i)
{
g.drawLine(0,i*diff_x,600,i*diff_x);
g.drawLine(i*diff_x,0,i*diff_x,600);
}
g.setColor(Color.white);
}
public void paint(Graphics g)
{
drawEmptyGrid(g);
g.setColor(Color.red);
for(int i=0;i<game.getGridSize();++i)
{
for(int j=0;j<game.getGridSize();++j)
{
if( game.grid[i][j] )
{
g.fillRect(i*diff_x,j*diff_y,diff_x,diff_y);
}
}
}
g.setColor(Color.white);
}
// This method will be called when the mouse has been clicked.
public void mouseClicked (MouseEvent me) {
// Save the coordinates of the click lke this.
xCo = me.getX();
yCo = me.getY();
int x_init = xCo / diff_x;
int y_init = yCo / diff_y;
System.out.println(x_init + "x" + y_init);
game.grid[x_init][y_init] = true;
//show the results of the click
repaint();
}
// This is called when the mous has been pressed
public void mousePressed (MouseEvent me) {}
// When it has been released
// not that a click also calls these Mouse-Pressed and Released.
// since they are empty nothing hapens here.
public void mouseReleased (MouseEvent me) {}
// This is executed when the mouse enters the applet. it will only
// be executed again when the mouse has left and then re-entered.
public void mouseEntered (MouseEvent me) {}
// When the Mouse leaves the applet.
public void mouseExited (MouseEvent me) {}
public void actionPerformed(ActionEvent evt)
{
// Here we will ask what component called this method
if (evt.getSource() == nextButton)
{
System.out.println("I got clicked!");
game.nextIteration();
repaint();
}
}
}
class GameOfLife
{
private final int GRID_SIZE = 64;
public boolean [][] grid = new boolean[GRID_SIZE][GRID_SIZE];
//default constructor
public GameOfLife()
{
for(int i=0;i<GRID_SIZE;++i)
{
for(int j=0;j<GRID_SIZE;++j)
{
grid[i][j] = false;
}
}
}
public int getGridSize()
{
return GRID_SIZE;
}
public int getLiveNeighbors(int i,int j)
{
int neighbors = 0;
for( int tmp_i = i-1; tmp_i <= i+1; ++tmp_i )
{
for( int tmp_j = j-1; tmp_j <= j+1; ++tmp_j )
{
if( tmp_i < 0 || tmp_i >= GRID_SIZE || tmp_j < 0 || tmp_j >= GRID_SIZE )
{}
else
{
if( grid[tmp_i][tmp_j] )
{
neighbors++;
}
}
}
}
return neighbors;
}
public void nextIteration()
{
boolean [][] newGrid = new boolean[GRID_SIZE][GRID_SIZE];
for(int i=0;i<GRID_SIZE;++i)
{
for(int j=0;j<GRID_SIZE;++j)
{
newGrid[i][j] = grid[i][j];
}
}
for( int i=0;i<GRID_SIZE;++i)
{
for( int j=0;j<GRID_SIZE;++j)
{
int my_neighbors = getLiveNeighbors(i,j);
if( !newGrid[i][j] && my_neighbors == 3)
{
grid[i][j] = true;
}
else if( newGrid[i][j] && ( my_neighbors == 2 || my_neighbors == 3 ) )
{
grid[i][j] = true;
}
else
{
grid[i][j] = false;
}
}
}
System.out.println("Change of assignment");
}
}
Further tips
Don't use AWT components in this millennium, use Swing instead.
Don't use null layouts. The custom rendered area does not need it, and the button should be sized and positioned using a layout manager (with maybe a border to pad it out).
Update
This code implements the 2nd suggestion from above 'use layouts', but leaves it as an exercise for the reader to update the components to something that might be used in this millennium (i.e. Swing).
The source below 'cheats' in a sense to show the GUI at it's natural size. This is tricky to do in an applet, since the size is set by the HTML. But put the GUI into a Swing based JOptionPane and it can be put on-screen, packed to its natural size, in just a couple of lines of code.
Here is what it looks like at the 'natural size' (I played with some numbers, to make the GUI smaller).
// <applet code='GameOfLifeApplet' width=320 height=350></applet>
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class GameOfLifeApplet extends Applet implements ActionListener
{
private Button nextButton = null;
private Ecosystem ecosystem;
public void init()
{
add(getGui());
}
public Component getGui() {
Panel gui = new Panel(new BorderLayout(3,3));
ecosystem = new Ecosystem();
gui.add(ecosystem, BorderLayout.CENTER);
nextButton = new Button("Next Stage");
Panel p = new Panel(new FlowLayout());
p.add(nextButton);
gui.add(p, BorderLayout.SOUTH);
nextButton.addActionListener(this);
return gui;
}
public void actionPerformed(ActionEvent evt)
{
// Here we will ask what component called this method
if (evt.getSource() == nextButton)
{
System.out.println("I got clicked!");
ecosystem.nextIteration();
ecosystem.repaint();
}
}
public static void main(String[] args) {
GameOfLifeApplet gola = new GameOfLifeApplet();
// quick cheat to get it on-screen (packed).
javax.swing.JOptionPane.showMessageDialog(null,gola.getGui());
}
}
class Ecosystem extends Panel implements MouseListener {
private GameOfLife game = new GameOfLife();
//the x and y coordinates to get the location of the clicked points
private int xCo, yCo;
private int diff_x, diff_y;
private int size = 300;
Ecosystem() {
diff_x = diff_y = 600 / game.getGridSize();
setPreferredSize(new Dimension(size,size));
addMouseListener(this);
}
public void nextIteration() {
game.nextIteration();
}
private void drawEmptyGrid(Graphics g)
{
g.setColor(Color.white);
g.fillRect(0,0,size,size);
g.setColor(Color.black);
for(int i=0;i<game.getGridSize();++i)
{
g.drawLine(0,i*diff_x,size,i*diff_x);
g.drawLine(i*diff_x,0,i*diff_x,size);
}
g.setColor(Color.white);
}
public void paint(Graphics g)
{
drawEmptyGrid(g);
g.setColor(Color.red);
for(int i=0;i<game.getGridSize();++i)
{
for(int j=0;j<game.getGridSize();++j)
{
if( game.grid[i][j] )
{
g.fillRect(i*diff_x,j*diff_y,diff_x,diff_y);
}
}
}
g.setColor(Color.white);
}
// This method will be called when the mouse has been clicked.
public void mouseClicked (MouseEvent me) {
Point point = me.getPoint();
// Save the coordinates of the click lke this.
xCo = (int)point.getX();
yCo = (int)point.getY();
int x_init = xCo / diff_x;
int y_init = yCo / diff_y;
System.out.println(x_init + "x" + y_init);
game.grid[x_init][y_init] = true;
//show the results of the click
repaint();
}
// This is called when the mous has been pressed
public void mousePressed (MouseEvent me) {}
// When it has been released
// not that a click also calls these Mouse-Pressed and Released.
// since they are empty nothing hapens here.
public void mouseReleased (MouseEvent me) {}
// This is executed when the mouse enters the applet. it will only
// be executed again when the mouse has left and then re-entered.
public void mouseEntered (MouseEvent me) {}
// When the Mouse leaves the applet.
public void mouseExited (MouseEvent me) {}
}
class GameOfLife
{
private final int GRID_SIZE = 60;
public boolean [][] grid = new boolean[GRID_SIZE][GRID_SIZE];
//default constructor
public GameOfLife()
{
for(int i=0;i<GRID_SIZE;++i)
{
for(int j=0;j<GRID_SIZE;++j)
{
grid[i][j] = false;
}
}
}
public int getGridSize()
{
return GRID_SIZE;
}
public int getLiveNeighbors(int i,int j)
{
int neighbors = 0;
for( int tmp_i = i-1; tmp_i <= i+1; ++tmp_i )
{
for( int tmp_j = j-1; tmp_j <= j+1; ++tmp_j )
{
if( tmp_i < 0 || tmp_i >= GRID_SIZE || tmp_j < 0 || tmp_j >= GRID_SIZE )
{}
else
{
if( grid[tmp_i][tmp_j] )
{
neighbors++;
}
}
}
}
return neighbors;
}
public void nextIteration()
{
boolean [][] newGrid = new boolean[GRID_SIZE][GRID_SIZE];
for(int i=0;i<GRID_SIZE;++i)
{
for(int j=0;j<GRID_SIZE;++j)
{
newGrid[i][j] = grid[i][j];
}
}
for( int i=0;i<GRID_SIZE;++i)
{
for( int j=0;j<GRID_SIZE;++j)
{
int my_neighbors = getLiveNeighbors(i,j);
if( !newGrid[i][j] && my_neighbors == 3)
{
grid[i][j] = true;
}
else if( newGrid[i][j] && ( my_neighbors == 2 || my_neighbors == 3 ) )
{
grid[i][j] = true;
}
else
{
grid[i][j] = false;
}
}
}
System.out.println("Change of assignment");
}
}
Other matters
The code moves the custom painting to a Panel. This gets around the common problems of painting directly to a top-level container. It also allows easy re-use of the same GUI in different containers. In this case both an applet, and for the 'application' (which would usually be put in a frame), a JOptionPane. It is now what is known as a 'hybrid applet/application' (easier for testing).
The custom painted component Ecosystem (shrugs) informs the layout what size it prefers to be. This helps us to avoid needing to set the size or bounds of anything.
The button will be exactly as big as it needs to be.
First, I think you're reading the exception trace wrong. The exception is an ArrayIndexOutOfBoundsException and occurs on line 63 of GameOfLifeApplet.java. That your app is an applet or that the exception occurs on the thread AWT-EventQueue-1 bears no relevance at all.
The root cause is that you've not properly synchronized the model and view's idea of how many cells there are in your grid. At the least, you should consider checking that the user actually clicked inside the grid before accessing the array element.
I have a JTable that is using a TableColumnModelListener() to detect when the column has been re-sized and I have some code I want to execute in the columnMarginChanged() method.
How do I determine whether the column was re-sized by the user or as a result of other code?
I am thinking I have to start with ChangeEvent.getSource() but I don't know where to go from there.
Thank you.
I can give you one possible approach. I was trying to solve the same problem, because I wanted to serialize information about column widths to disk, so that the next time the table opened up in my application, I could restore the column widths appropriately. Here goes:
Step 1 - Override your JTable and add a boolean property to it
class MyTable extends JTable {
private boolean isColumnWidthChanged;
public boolean getColumnWidthChanged() {
return isColumnWidthChanged;
}
public void setColumnWidthChanged(boolean widthChanged) {
isColumnWidthChanged = widthChanged;
}
}
Step 2 - Add a TableColumnModelListener() to the table
private class TableColumnWidthListener implements TableColumnModelListener
{
#Override
public void columnMarginChanged(ChangeEvent e)
{
/* columnMarginChanged is called continuously as the column width is changed
by dragging. Therefore, execute code below ONLY if we are not already
aware of the column width having changed */
if(!tableObj.hasColumnWidthChanged())
{
/* the condition below will NOT be true if
the column width is being changed by code. */
if(tableObj.getTableHeader.getResizingColumn() != null)
{
// User must have dragged column and changed width
tableObj.setColumnWidthChanged(true);
}
}
}
#Override
public void columnMoved(TableColumnModelEvent e) { }
#Override
public void columnAdded(TableColumnModelEvent e) { }
#Override
public void columnRemoved(TableColumnModelEvent e) { }
#Override
public void columnSelectionChanged(ListSelectionEvent e) { }
}
Step 3 - Add a mouse listener to the table header
private class TableHeaderMouseListener extends MouseAdapter
{
#Override
public void mouseReleased(MouseEvent e)
{
/* On mouse release, check if column width has changed */
if(tableObj.getColumnWidthChanged())
{
// Do whatever you need to do here
// Reset the flag on the table.
tableObj.setColumnWidthChanged(false);
}
}
}
NOTE: In my application, the TableHeaderMouseListener and TableColumnWidthListener classes were private inner classes of my main application class. My main application class held on to a reference of the table being observed. Therefore, these inner classes had access to the table instance. Obviously, depending on your setup, you need to do the appropriate thing to make the table instance available to these other classes. Hope this helps!
Typically you want to be notified of a completed column drag, or a completed column resize. E.g.
interface SGridModel {
public void columnMoved(int oldLocation, int newLocation);
public void columnResized(int column, int newWidth);
}
This isn't pretty but it will do what you want:
class SColumnListener extends MouseAdapter implements TableColumnModelListener {
private final Logger log = Logger.getLogger(getClass());
private final SGridModel model;
private int oldIndex = -1;
private int newIndex = -1;
private boolean dragging = false;
private boolean resizing = false;
private int resizingColumn = -1;
private int oldWidth = -1;
SColumnListener(SGridModel model){
this.model = model;
}
#Override
public void mousePressed(MouseEvent e) {
// capture start of resize
if(e.getSource() instanceof JTableHeader) {
TableColumn tc = ((JTableHeader)e.getSource()).getResizingColumn();
if(tc != null) {
resizing = true;
resizingColumn = tc.getModelIndex();
oldWidth = tc.getPreferredWidth();
} else {
resizingColumn = -1;
oldWidth = -1;
}
}
}
#Override
public void mouseReleased(MouseEvent e) {
// column moved
if(dragging && oldIndex != newIndex) {
model.columnMoved(oldIndex, newIndex);
log.info("column moved: " +oldIndex+" -> "+newIndex);
}
dragging = false;
oldIndex = -1;
newIndex = -1;
// column resized
if(resizing) {
if(e.getSource() instanceof JTableHeader) {
TableColumn tc = ((JTableHeader)e.getSource()).getColumnModel().getColumn(resizingColumn);
if(tc != null) {
int newWidth = tc.getPreferredWidth();
if(newWidth != oldWidth) {
model.columnResized(resizingColumn, newWidth);
log.info("column resized: " +resizingColumn+" -> "+newWidth);
}
}
}
}
resizing = false;
resizingColumn = -1;
oldWidth = -1;
}
#Override
public void columnAdded(TableColumnModelEvent e) {
}
#Override
public void columnRemoved(TableColumnModelEvent e) {
}
#Override
public void columnMoved(TableColumnModelEvent e) {
// capture dragging
dragging = true;
if(oldIndex == -1){
oldIndex = e.getFromIndex();
}
newIndex = e.getToIndex();
}
#Override
public void columnMarginChanged(ChangeEvent e) {
}
#Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
}
add it to the table as follows:
table.getColumnModel().addColumnModelListener(cl);
table.getTableHeader().addMouseListener(cl);
A simpler solution might just be to listen on the mouse release event (which happens only once in this user interaction) and check if the column-sizes changed in the meantime ? I'm using the code below to listen for column reordering and size changes.
getTableHeader().addMouseListener( new MouseAdapter() {
public void mouseReleased(MouseEvent arg0)
{
String colNamesAndSizes = "";
for( int i=0;i<getColumnModel().getColumnCount();i++ ) {
if( i>0 ) colNamesAndSizes += ",";
TableColumn column = getColumnModel().getColumn(i);
colNamesAndSizes += column.getHeaderValue();
colNamesAndSizes += ":";
colNamesAndSizes += column.getWidth();
}
// check if changed, if yes, persist...
}});
You can also add PropertyChangeListener on each TableColumn to capture changes on width and preferredWidth properties.
I would like to ask the same thing than this question but using SWT: Is there a way to make a Button with your own button graphic not just with an image inside the button? If not is another way to create a custom button in java?
public class ImageButton extends Canvas {
private int mouse = 0;
private boolean hit = false;
public ImageButton(Composite parent, int style) {
super(parent, style);
this.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
switch (mouse) {
case 0:
// Default state
e.gc.drawString("Normal", 5, 5);
break;
case 1:
// Mouse over
e.gc.drawString("Mouse over", 5, 5);
break;
case 2:
// Mouse down
e.gc.drawString("Hit", 5, 5);
break;
}
}
});
this.addMouseMoveListener(new MouseMoveListener() {
public void mouseMove(MouseEvent e) {
if (!hit)
return;
mouse = 2;
if (e.x < 0 || e.y < 0 || e.x > getBounds().width
|| e.y > getBounds().height) {
mouse = 0;
}
redraw();
}
});
this.addMouseTrackListener(new MouseTrackAdapter() {
public void mouseEnter(MouseEvent e) {
mouse = 1;
redraw();
}
public void mouseExit(MouseEvent e) {
mouse = 0;
redraw();
}
});
this.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
hit = true;
mouse = 2;
redraw();
}
public void mouseUp(MouseEvent e) {
hit = false;
mouse = 1;
if (e.x < 0 || e.y < 0 || e.x > getBounds().width
|| e.y > getBounds().height) {
mouse = 0;
}
redraw();
if (mouse == 1)
notifyListeners(SWT.Selection, new Event());
}
});
this.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if (e.keyCode == '\r' || e.character == ' ') {
Event event = new Event();
notifyListeners(SWT.Selection, event);
}
}
});
}
}
No, you can add a PaintListener to a button, but it will probably look really strange.
What you would need to do is to set the style of the window to "owner drawn" and than add your drawing code in the Button#wmDrawChild method. This means you need to add dependencies on internal SWT-classes and it will only work for Windows.