MigLayout + JScrollPane width issue - java

I want to imitate a table with scroll bar, but there is a problem with width.
Here is an extremely simplified code (but it is complete application, so you can compile it easilly and see everything at first hand).
package view;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import net.miginfocom.layout.AC;
import net.miginfocom.layout.CC;
import net.miginfocom.layout.LC;
import net.miginfocom.swing.MigLayout;
public class AppView
{
private final JFrame main_frame;
public AppView()
{
main_frame = new JFrame();
main_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
main_frame.setTitle("Example");
JPanel main_panel = new JPanel() {
private static final long serialVersionUID = 1L;
public Dimension getPreferredSize()
{
return new Dimension(300, 200);
}
};
main_panel.setLayout(new MigLayout("fill"));
JPanel table_panel = new JPanel();
table_panel.setLayout(new MigLayout(new LC(), new AC().gap("0"), new AC().gap("0")));
JScrollPane scroll_pane = new JScrollPane(table_panel);
scroll_pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
int[] width_arr = {20, 20, 20, 20, 20};
for (int i = 0; i < 60; i++) {
for (int j = 0; j < width_arr.length; j++) {
if (j == width_arr.length - 1)
table_panel.add(new JLabel(i + 1 + ", " + (j + 1)), new CC().width(width_arr[j] + "%").wrap());
else
table_panel.add(new JLabel(i + 1 + ", " + (j + 1)), new CC().width(width_arr[j] + "%"));
}
}
main_frame.getContentPane().add(main_panel, BorderLayout.CENTER);
main_panel.add(scroll_pane, new CC().grow());
main_frame.pack();
main_frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new AppView();
}
});
}
}
As you can see, I create a table_panel and wrap it with scroll_pane. Then I add 60 rows with 5 JLabels in each row. The problem is that if I specify each column's width so that total row width is 100%, width of scroll element starts to increase rapidly.
Here are two screenshots - one for case when horizontal scroll is disabled and another for case when horizontal scroll is enabled.
My suggestion about such behavior is that width of JScrollPane element is extremely large, but it seems that this suggestion is incorrect, because if I define width for each column so that total width is 98%, everything works fine.
Please, help me with understanding these weird things. Thanks in advance.
[UPDATE]
I decided to use another approach to achieve what I need. This approach is based on grid functionallity. There is a small condition - all width values should be multiple of 5. I suppose this condition is not limiting, because it's hard to imagine situations, where you need more than 5% precision.
First, I add 20 invisible elements (for further calls of span() method):
Box empty;
for (int i = 0; i < 20; i++) {
empty = new Box(BoxLayout.X_AXIS);
empty.setVisible(false);
if (i == 19)
table_panel.add(empty, "wrap");
else
table_panel.add(empty);
}
Without these elements, span() method will have no effect.
Here is full code sample:
package view;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import net.miginfocom.layout.AC;
import net.miginfocom.layout.CC;
import net.miginfocom.layout.LC;
import net.miginfocom.swing.MigLayout;
public class AppView
{
private final JFrame main_frame;
public AppView()
{
main_frame = new JFrame();
main_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
main_frame.setTitle("Example");
JPanel main_panel = new JPanel() {
private static final long serialVersionUID = 1L;
public Dimension getPreferredSize()
{
return new Dimension(300, 200);
}
};
main_panel.setLayout(new MigLayout("fill"));
JPanel table_panel = new JPanel();
table_panel.setLayout(new MigLayout(new LC().fill().debug(1).hideMode(2), new AC().gap("0"), new AC().gap("0")));
JScrollPane scroll_pane = new JScrollPane(table_panel);
scroll_pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
Box empty;
for (int i = 0; i < 20; i++) {
empty = new Box(BoxLayout.X_AXIS);
empty.setVisible(false);
if (i == 19)
table_panel.add(empty, "wrap");
else
table_panel.add(empty);
}
int[] width_arr = {20, 20, 20, 20, 20};
for (int i = 0; i < 60; i++)
for (int j = 0; j < width_arr.length; j++) {
if (j == width_arr.length - 1)
table_panel.add(new JLabel(i + 1 + ", " + (j + 1)), new CC().span(width_arr[j] / 5).wrap());
else
table_panel.add(new JLabel(i + 1 + ", " + (j + 1)), new CC().span(width_arr[j] / 5));
}
main_frame.getContentPane().add(main_panel, BorderLayout.CENTER);
main_panel.add(scroll_pane, new CC().grow());
main_frame.pack();
main_frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new AppView();
}
});
}
}
This works practically fine, and horizontal scroll isn't required anymore, but there is the problem with width of invisible boxes - their width is not constant and equal for all boxes, it depends on JLabels width. So proportions are not correct.
And here are some screenshots, illustrating that:
1) Simplest case: five columns, 20% width each, works fine.
2) More complicated example: three columns - 10%, 40%, 50%. Proportions are invalid, because second column isn't 4 times wider than first.
Well, now I have no idea what to do next. And all my attempts to find something on the Internet were in vain.

Related

ordering image and text in jbutton

I am attempting to overlay the text on a JButton over an ImageIcon that is behind it. However, when the imageIcon is added, the text dissapears. Is there any way to specify the order in which it displays?
Below, i have tried to separately add the images and text to see if that would affect it, but no luck.
Can anyone help me out?
private void initButtons() {
int locationX = 0, locationY = 525;
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
boardArray[x][y] = new ChessButton();
boardArray[x][y].setSize(75, 75);
boardArray[x][y].setLocation(locationX, locationY);
boardArray[x][y].setXAndY(x, y);
if ((x % 2 == 0 && y % 2 == 1) || (x % 2 == 1 && y % 2 == 0)) {
boardArray[x][y].setColour("white");
boardArray[x][y].setIcon(new ImageIcon("Assets/white_null_null.png"));
} else {
boardArray[x][y].setColour("black");
boardArray[x][y].setIcon(new ImageIcon("Assets/black_null_null.png"));
}
//this adds the images in an alternating pattern
chessFrame.add(boardArray[x][y]);
locationX = locationX + 75;
}
locationX = 0;
locationY = locationY - 75;
}
}
void initPieces() {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
if ((x % 2 == 0 && y % 2 == 1) || (x % 2 == 1 && y % 2 == 0)) {
boardArray[x][y].setFont(new Font("Arial Unicode MS", Font.PLAIN, 40));
boardArray[x][y].setText("\u2654");//sets a particular chess piece as text, just testing it now.
} else {
boardArray[x][y].setFont(new Font("Arial Unicode MS", Font.PLAIN, 40));
boardArray[x][y].setText("\u2654");//sets a particular chess piece as text, just testing it now.
//this is suposed to overlay the image over the text, but it is not.
}
}
}
}
First of all, I see you're calling this method: boardArray[x][y].setLocation(locationX, locationY);
.setLocation(...) indicates you're using a null layout, please read Null layout is evil and Why is it frowned upon to use a null layout in Swing? to know why you should avoid its use.
For creating a Chess board, I'd be using GridLayout, please read how to use the different layout managers
Now, to overlay text over the icon, you only need to call JButton#setHorizontalAlignment(...) method and pass SwingConstants.CENTER as the parameter.
For example:
import java.awt.Color;
import java.awt.Font;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
public class ButtonWithImageAndText {
private JFrame frame;
private JButton button;
private Icon icon;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new ButtonWithImageAndText().createAndShowGui());
}
private void createAndShowGui() {
frame = new JFrame(getClass().getSimpleName());
try {
icon = new ImageIcon(ImageIO.read(getClass().getResource("up.jpg")));
} catch (IOException e) {
e.printStackTrace();
}
button = new JButton();
button.setIcon(icon);
button.setText("Click me!");
button.setHorizontalTextPosition(SwingConstants.CENTER);
button.setFont(new Font("Arial", Font.PLAIN, 40));
button.setForeground(Color.RED);
frame.add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
Which gives the following output:
For your future questions, please read how to make a valid Minimal, Complete and Verifiable Example (MCVE) that demonstrates your issue and your best try to solve it yourself, the code I posted is a good example of it, as you can copy-paste it and see the same result as I
Use the setComponentZOrder(...) method of the Container class. This will enable you to set the Z-index of the components to set them in what ever order you like. Look also this

Timer speeds up when moving mouse over Chrome webpage elements

I have created a time lapse to show the density on a highway over the course of a day. The data is held in a double[][] array data, where data.length is 2880 (each index represents a 30-second interval) and data[0].length is about 450 (representing a cubic interpolation across the highway section's length).
My code for the time lapse is as follows:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.Timer;
public class TimeLapse extends JPanel {
static double[][] data;
int index = 0;
double max;
double lineWidth;
Timer timer;
final JProgressBar progressBar = new JProgressBar(0, 2879);
BufferedImage[] images;
Color colors[][];
public static void main(String[] args) {
data=getData(); //arbitrary method to get interpolated data
new TimeLapse(data, 90);
}
public TimeLapse(double[][] data1, double max) {
data = data1;
this.max = max;
JFrame frame = new JFrame("Timelapse");
frame.setPreferredSize(new Dimension(800, 600));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.anchor = GridBagConstraints.NORTH;
c.gridwidth = 1;
c.gridx = 0;
c.gridheight = 1;
c.weightx = 1;
c.weighty = .01;
c.gridy = 0;
frame.add(progressBar, c);
c.anchor = GridBagConstraints.SOUTH;
c.gridy = 1;
c.gridheight = 9;
c.weighty = 1;
frame.add(this, c);
frame.pack();
getColorArray();
frame.setVisible(true);
int dataLength;
dataLength = data.length;
// Make the animation 5 seconds long
int delay = (int) (5000d / dataLength);
timer = new Timer(delay, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateIndex();
repaint();
Toolkit.getDefaultToolkit().sync();
}
});
timer.start();
}
private void getColorArray() {
double cutOff = max / 2;
colors = new Color[data.length][data[0].length];
for (int index = 0; index < data.length; index++) {
for (double x = 0; x < data[0].length; x++) {
colors[index][(int) x] =
getColor(data[index][(int) x], cutOff);
}
}
}
private void updateIndex() {
index = index < data.length - 1 ? index + 1 : 0;
progressBar.setValue(2879 * index / data.length);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
int panelHeight = getHeight();
lineWidth = ((double) getWidth()) / ((double) (data[0].length));
// guaranteed counter, as doing it in timer's ActionListener could overlap with rendering
for (double x = 0; x < data[0].length; x++) {
g2.setColor(colors[index][(int) x]);
double rectHeight = panelHeight * data[index][(int) x] / max;
g2.fillRect((int) (x * lineWidth),
(int) (panelHeight - rectHeight),
(int) (lineWidth + 1), (int) rectHeight + 1);
}
g2.dispose();
}
private Color getColor(double value, double cutOff) {
int hueR, hueG, hueB = 0;
if (value < cutOff) {
hueG = 255;
hueR = (int) (255 * value / (cutOff));
} else if (max != cutOff) {
hueR = 255;
hueG = (int) (255 - (255 * (value - cutOff) / (max - cutOff)));
} else {
hueR = 255;
hueG = 0;
}
hueR = (hueR < 0) ? 0 : ((hueR > 255) ? 255 : hueR);
hueG = (hueG < 0) ? 0 : ((hueG > 255) ? 255 : hueG);
hueB = (hueB < 0) ? 0 : ((hueB > 255) ? 255 : hueB);
return new Color(hueR, hueG, hueB);
}
}
The animation functions smoothly, but it typically takes a great deal longer than the five seconds I set it to, which I chalk up to the constant coloring and generation of hundreds of lines in the panel.
To verify that I was correct and it was indeed much slower than it should be, I used the Google Chrome widget that appears when you Google "stopwatch" to time it. Doing this I found that when I ran the stopwatch the animation sped up greatly, as well as whenever I moved my mouse over certain elements (hyperlinks, the tabs at the top, and seemingly anything else that gives a visual response to the mouse hovering). This only happens when I move the mouse or am running the stopwatch; keeping the mouse still does not speed it up, and it appears to only have this behavior while hovering over Chrome (i.e. any other application is fine). Can anyone explain this odd behavior?
EDIT: It also happens while reloading a tab, but not after it's done reloading.
EDIT 2: I now know for certain that the timer is speeding up. I created a small class with a timer that prints every millisecond an increasing index:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class TimerTest {
static int index = 0;
public static void main(String[] args) {
Timer t = new Timer(1, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(index++);
}
});
t.start();
while (true) {
}
}
}
This has exactly the same behavior as the time lapse class, speeding up greatly when moving the mouse over Chrome's elements. I believe the reason is that, in this case, println is a slow method and executes slower than the timer updates. Again, can someone explain why Chrome specifically speeds up a backed-up timer?
A painting method is for painting only.
You should not be changing properties of your class in the painting method.
That is you can not control when Swing determines a component needs to be repainted. So there may be some system call that is causing the component to be repainted and therefore changing your properties more frequently than you think.
For example you should not be updating your "index" variable or the progress bar value. Instead your Timer should invoke a method to changes these properties and then that method should invoke repaint on the panel.
This only happens when I move the mouse
Maybe you have tooltips on the panel which would cause it to be repainted.
This is easy to test, just add a System.out.println(...) statement to the paintComponent() method to see if it displays more frequently than the 5 seconds of your Timer.

Changing colors of GridLayout elements

Note: this question may look a bit like another I've posted a few weeks ago. Back then I was not working with adding the buttons as arrays, thats what makes it more difficult for me this time.
I'm working with a chessgame, and I have been able to set up a board of 64 squares on my own. However it seems to be a little too complicated for me to manage adding the colors to the squares.
My code looks like this:
Chess.java
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Chess implements config {
public static void main(String[] args) {
int[] squareArray;
squareArray = new int[65];
int i = 1;
JFrame frame = new JFrame("Chessboard");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(ROWS, COLS, 2, 2));
for (i = 1; i < 65; i++) {
squareArray[i] = i;
frame.add(new JButton("" + squareArray[i]));
}
frame.setSize(800, 800);
frame.setVisible(true);
}
}
Piece.java
import java.awt.Color;
import javax.swing.JFrame;
public class Piece extends JFrame implements config {
public Piece (int n) {
setBackground(calcColor(n));
}
public void Pieces() {
new Pieces();
//This class contains nothing at the moment.
}
Color calcColor(int n) {
boolean everysecondSquare = (n % 2 == 0);
boolean everysecondRow = ((n / ROWS) % 2 == 0);
return (everysecondSquare != everysecondRow ? P1Color : P2Color);
}
}
config.java
import java.awt.Color;
public interface config {
public int ROWS = 8;
public int COLS = 8;
Color P1Color = (new Color(245,222,179));
Color P2Color = (new Color(244,164,96));
}
I'm very aware that this probably is pretty bad coded as I am very new to Java. I would be very happy and thankful if someone could help me out with the colors here as I have been stuck for several days now without getting any further. I don't expect someone to finish the code for me, but merely help me on the way to get there. :)
What about this?
for (i = 1; i < 65; i++) {
squareArray[i] = i;
JButton b=new JButton("" + squareArray[i]);
b.setBackground(desiredColorHere);
frame.add(b);
}

Rounding Inaccuracies When Combining Areas in Java?

I'm working with Areas in Java.
My test program draws three random triangles and combines them to form one or more polygons. After the Areas are .add()ed together, I use PathIterator to trace the edges.
Sometimes, however, the Area objects will not combine as they should... and as you can see in the last image I posted, extra edges will be drawn.
I think the problem is caused by rounding inaccuracies in Java's Area class (when I debug the test program, the Area shows the gaps before the PathIterator is used), but I don't think Java provides any other way to combine shapes.
Any solutions?
Example code and images:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
public class AreaTest extends JFrame{
private static final long serialVersionUID = -2221432546854106311L;
Area area = new Area();
ArrayList<Line2D.Double> areaSegments = new ArrayList<Line2D.Double>();
AreaTest() {
Path2D.Double triangle = new Path2D.Double();
Random random = new Random();
// Draw three random triangles
for (int i = 0; i < 3; i++) {
triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.closePath();
area.add(new Area(triangle));
}
// Note: we're storing double[] and not Point2D.Double
ArrayList<double[]> areaPoints = new ArrayList<double[]>();
double[] coords = new double[6];
for (PathIterator pi = area.getPathIterator(null); !pi.isDone(); pi.next()) {
// Because the Area is composed of straight lines
int type = pi.currentSegment(coords);
// We record a double array of {segment type, x coord, y coord}
double[] pathIteratorCoords = {type, coords[0], coords[1]};
areaPoints.add(pathIteratorCoords);
}
double[] start = new double[3]; // To record where each polygon starts
for (int i = 0; i < areaPoints.size(); i++) {
// If we're not on the last point, return a line from this point to the next
double[] currentElement = areaPoints.get(i);
// We need a default value in case we've reached the end of the ArrayList
double[] nextElement = {-1, -1, -1};
if (i < areaPoints.size() - 1) {
nextElement = areaPoints.get(i + 1);
}
// Make the lines
if (currentElement[0] == PathIterator.SEG_MOVETO) {
start = currentElement; // Record where the polygon started to close it later
}
if (nextElement[0] == PathIterator.SEG_LINETO) {
areaSegments.add(
new Line2D.Double(
currentElement[1], currentElement[2],
nextElement[1], nextElement[2]
)
);
} else if (nextElement[0] == PathIterator.SEG_CLOSE) {
areaSegments.add(
new Line2D.Double(
currentElement[1], currentElement[2],
start[1], start[2]
)
);
}
}
setSize(new Dimension(500, 500));
setLocationRelativeTo(null); // To center the JFrame on screen
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
setVisible(true);
}
public void paint(Graphics g) {
// Fill the area
Graphics2D g2d = (Graphics2D) g;
g.setColor(Color.lightGray);
g2d.fill(area);
// Draw the border line by line
g.setColor(Color.black);
for (Line2D.Double line : areaSegments) {
g2d.draw(line);
}
}
public static void main(String[] args) {
new AreaTest();
}
}
A successful case:
A failing case:
Here:
for (int i = 0; i < 3; i++) {
triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.closePath();
area.add(new Area(triangle));
}
you are adding in fact
1 triangle in the first loop
2 triangles in the second loop
3 triangles in the third loop
This is where your inaccuracies come from. Try this and see if your problem still persists.
for (int i = 0; i < 3; i++) {
triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.closePath();
area.add(new Area(triangle));
triangle.reset();
}
Note the path reset after each loop.
EDIT: to explain more where the inaccuracies come from here the three paths you try to combine. Which makes it obvious where errors might arise.
I've re-factored your example to make testing easier, adding features of both answers. Restoring triangle.reset() seemed to eliminate the artifatcts for me. In addition,
Build the GUI on the event dispatch thread.
For rendering, extend a JComponent, e.g. JPanel, and override paintComponent().
Absent subcomponents having a preferred size, override getPreferredSize().
Use RenderingHints.
SSCCE:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/** #see http://stackoverflow.com/q/9526835/230513 */
public class AreaTest extends JPanel {
private static final int SIZE = 500;
private static final int INSET = SIZE / 10;
private static final int BOUND = SIZE - 2 * INSET;
private static final int N = 5;
private static final AffineTransform I = new AffineTransform();
private static final double FLATNESS = 1;
private static final Random random = new Random();
private Area area = new Area();
private List<Line2D.Double> areaSegments = new ArrayList<Line2D.Double>();
private int count = N;
AreaTest() {
setLayout(new BorderLayout());
create();
add(new JPanel() {
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.lightGray);
g2d.fill(area);
g.setColor(Color.black);
for (Line2D.Double line : areaSegments) {
g2d.draw(line);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
});
JPanel control = new JPanel();
control.add(new JButton(new AbstractAction("Update") {
#Override
public void actionPerformed(ActionEvent e) {
create();
repaint();
}
}));
JSpinner countSpinner = new JSpinner();
countSpinner.setModel(new SpinnerNumberModel(N, 3, 42, 1));
countSpinner.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
JSpinner s = (JSpinner) e.getSource();
count = ((Integer) s.getValue()).intValue();
}
});
control.add(countSpinner);
add(control, BorderLayout.SOUTH);
}
private int randomPoint() {
return random.nextInt(BOUND) + INSET;
}
private void create() {
area.reset();
areaSegments.clear();
Path2D.Double triangle = new Path2D.Double();
// Draw three random triangles
for (int i = 0; i < count; i++) {
triangle.moveTo(randomPoint(), randomPoint());
triangle.lineTo(randomPoint(), randomPoint());
triangle.lineTo(randomPoint(), randomPoint());
triangle.closePath();
area.add(new Area(triangle));
triangle.reset();
}
// Note: we're storing double[] and not Point2D.Double
List<double[]> areaPoints = new ArrayList<double[]>();
double[] coords = new double[6];
for (PathIterator pi = area.getPathIterator(I, FLATNESS);
!pi.isDone(); pi.next()) {
// Because the Area is composed of straight lines
int type = pi.currentSegment(coords);
// We record a double array of {segment type, x coord, y coord}
double[] pathIteratorCoords = {type, coords[0], coords[1]};
areaPoints.add(pathIteratorCoords);
}
// To record where each polygon starts
double[] start = new double[3];
for (int i = 0; i < areaPoints.size(); i++) {
// If we're not on the last point, return a line from this point to the next
double[] currentElement = areaPoints.get(i);
// We need a default value in case we've reached the end of the List
double[] nextElement = {-1, -1, -1};
if (i < areaPoints.size() - 1) {
nextElement = areaPoints.get(i + 1);
}
// Make the lines
if (currentElement[0] == PathIterator.SEG_MOVETO) {
// Record where the polygon started to close it later
start = currentElement;
}
if (nextElement[0] == PathIterator.SEG_LINETO) {
areaSegments.add(
new Line2D.Double(
currentElement[1], currentElement[2],
nextElement[1], nextElement[2]));
} else if (nextElement[0] == PathIterator.SEG_CLOSE) {
areaSegments.add(
new Line2D.Double(
currentElement[1], currentElement[2],
start[1], start[2]));
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame();
f.add(new AreaTest());
f.pack();
f.setLocationRelativeTo(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setResizable(false);
f.setVisible(true);
}
});
}
}
I played around with this, and found a hacky way of getting rid of these. I'm not 100% sure that this will work in all cases, but it might.
After reading that the Area.transform's JavaDoc mentions
Transforms the geometry of this Area using the specified
AffineTransform. The geometry is transformed in place, which
permanently changes the enclosed area defined by this object.
I had a hunch and added possibility of rotating the Area by holding down a key. As the Area was rotating, the "inward" edges started to slowly disappear, until only the outline was left. I suspect that the "inward" edges are actually two edges very close to each other (so they look like a single edge), and that rotating the Area causes very small rounding inaccuracies, so the rotating sort of "melts" them together.
I then added a code to rotate the Area in very small steps for a full circle on keypress, and it looks like the artifacts disappear:
The image on the left is the Area built from 10 different random triangles (I upped the amount of triangles to get "failing" Areas more often), and the one on the right is the same Area, after being rotated full 360 degrees in very small increments (10000 steps).
Here's the piece of code for rotating the area in small steps (smaller amounts than 10000 steps would probably work just fine for most cases):
final int STEPS = 10000; //Number of steps in a full 360 degree rotation
double theta = (2*Math.PI) / STEPS; //Single step "size" in radians
Rectangle bounds = area.getBounds(); //Getting the bounds to find the center of the Area
AffineTransform trans = AffineTransform.getRotateInstance(theta, bounds.getCenterX(), bounds.getCenterY()); //Transformation matrix for theta radians around the center
//Rotate a full 360 degrees in small steps
for(int i = 0; i < STEPS; i++)
{
area.transform(trans);
}
As I said before, I'm not sure if this works in all cases, and the amount of steps needed might be much smaller or larger depending on the scenario. YMMV.

Points not being properly read by MouseListener

I'm having a problem where I can't properly access my instance Point data.
I create an multi-dimensional array of GridPanels, and instantiate each with a Point.
When first created, everything works as expected.
pic1 http://img.skitch.com/20100218-fciwr7t73ci2gajafmfxa2yf9q.jpg
When I click on a GridPanel however, the Listener class always receives the Point from the last GridPanel that was created ( (3, 3) in this case.)
When I pass an int instead of a Point however, the int for the GridPanel that was clicked is shown (like you'd expect).
Anyone know what's going on here?
Thanks
import javax.swing.JFrame;
/**
* Driver class.
*/
public class Test {
/**
* The main method.
* #param args Command line arguments.
*/
public static void main(String[] args) {
JFrame frame = new JFrame("TEST");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TestPanel panel = new TestPanel();
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
}
}
import java.awt.GridLayout;
import java.awt.Point;
import javax.swing.JPanel;
/**
* Creates a 4 by 4 grid of GridPanels.
*/
public class TestPanel extends JPanel {
static final int ROW_SIZE = 4;
static final int COL_SIZE = 4;
private GridPanel[][] g = new GridPanel[ROW_SIZE][COL_SIZE];
public TestPanel() {
Point coords = new Point();
setLayout(new GridLayout(ROW_SIZE, COL_SIZE));
for (int i = 0; i < ROW_SIZE; i++) {
for (int j = 0; j < COL_SIZE; j++) {
coords.setLocation(i, j);
g[i][j] = new GridPanel(coords);
add(g[i][j]);
}
}
}
}
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
* Contains the MouseListener.
*/
public class GridPanel extends JPanel {
private JLabel label;
private Point p;
public GridPanel(Point p) {
this.p = p;
label = new JLabel("" + p);
add(label);
setBackground(Color.WHITE);
setPreferredSize(new Dimension(200, 50));
addMouseListener(new SelectListener());
}
private class SelectListener extends MouseAdapter {
public void mousePressed(MouseEvent e) {
label.setText("" + p);
}
}
}
The problem is that you are re-using the same point, stored in coords. You need to create a new point for each grid element. It looks to you as if each panel has a different point value stored in it because each panel has a different label. But in the line
label = new JLabel("" + p);
you are creating a String that contains the current value of p. But p can change later, and the label won't change with it.
So the easiest fix for your problem is to change the line
this.p = p;
to
this.p = new Point(p); // Create a defensive copy.
It looks like you may be currently somewhat confused about the difference between objects and fields. For example,
Point p = new Point(3, 4);
Point p2 = p;
p.x = 7;
System.out.println(p2.x);
will yield 7, as there is only one point being manipulated, though it's pointed to by two fields. Using = doesn't create a copy of the point.
(Apologies if I'm explaining things you already know!)
The point is and object so it is passed by reference. This means all your panels reference the same Point. Since you are changing the location on it all the time - the last will be shown.
You have to create new Point every time in the loop:
public TestPanel() {
setLayout(new GridLayout(ROW_SIZE, COL_SIZE));
for (int i = 0; i < ROW_SIZE; i++) {
for (int j = 0; j < COL_SIZE; j++) {
g[i][j] = new GridPanel(new Point(i, j));
add(g[i][j]);
}
}
}
i change setPreferredSize(new Dimension(w, h)); this is done.
but in my program i need change my frame size every time. so how can fit gridpanel in that case.... if frame size (1200,800) or (1170,920) i am not using JLabel here.
thankyou for answering
in place of this frame.pack(); i use frame.setSize(W,H);
in a gridpanel add
setPreferredSize(new Dimension(x,y));
setBorder(BorderFactory.createLineBorder(Color.red));
i remove JLabel
where x = w / col_size; y = h / row_size;
now when i run Test.java grid are not fitted in my frame;

Categories

Resources