JScrollPane "jumping" when scrollbars start being used - java

Sorry, this is a long piece of sample code below, but here's the issue:
I have a background that I'm actively drawing (I could probably be smart and draw it once and just scale it, but this shows the problem just as well).
You can use the mouse wheel to zoom in and out on the image.
The idea is to do a "google map" zoom where it zooms under the mouse pointer. What I've noticed is that it doesn't seem to actuall behave until the image is big enough to use both scroll bars. Until then, you get the image simply getting bigger, but locked to the origin.
The "correct" behavior should be that the viewposition is moved, even though the scrollbars aren't yet being utilized by an oversized image.
I'm not sure how to get around this (or if this is correct and expected) without drawing a much larger background behind the image so it more than fills the viewport.
It "jumps" after one or the other scroll bars engages due to (I think) the same issue.
Thoughts?
package com.hostigr.raw.io.client.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
public class TestFrame extends JFrame {
public static void main(String[] arg) {
new TestFrame();
}
public TestFrame() {
initComponents();
setVisible(true);
}
private void initComponents() {
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(600, 600);
setPreferredSize(new Dimension(600, 600));
add(new TopPanel());
}
private class TopPanel extends JPanel {
JScrollPane scrollPane;
public TopPanel() {
setPreferredSize(new Dimension(500,500));
scrollPane = new JScrollPane(new InteriorPanel());
scrollPane.setPreferredSize(new Dimension(500,500));
scrollPane.getVerticalScrollBar().setPreferredSize(new Dimension(10,490));
scrollPane.getHorizontalScrollBar().setPreferredSize(new Dimension(490,10));
scrollPane.setWheelScrollingEnabled(false);
scrollPane.setHorizontalScrollBarPolicy(
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
add(scrollPane);
}
}
private class InteriorPanel extends JPanel {
private double scale = 10.0;
private final double scaleModifier = 0.1;
private final int width = 10;
private Point loc = new Point(0,0);
private final int SIZE = 10;
public InteriorPanel() {
super(true);
setPreferredSize(new Dimension((int)(scale * width * SIZE),
(int)(scale * width * SIZE)));
this.addMouseWheelListener(new MapMouseWheelListener());
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2D.scale(scale,scale);
for (int row = 0; row <= SIZE; row++) {
for (int col = 0; col < SIZE; col++) {
if ((col + row) % 2 == 0) {
g2D.setColor(Color.white);
} else {
g2D.setColor(Color.black);
}
g2D.fillRect(col * width, row * width, width, width);
}
}
}
private void incrementScale(int notches) {
double modifier = 0;
double prevScale = scale;
if (notches != 0) {
modifier = 1.0 + -notches / Math.abs(notches) * scaleModifier;
}
scale = scale * Math.pow(modifier, Math.abs(notches));
/*if (scale * width < 1) {
scale = 1.0/width;
} else if (scale * width * 3 > parentHeight || scale * width * 3 > parentWidth) {
if (parentHeight > parentWidth) {
scale = parentWidth / 3.0 / width;
} else {
scale = parentHeight / 3.0 / width;
}
} else if (scale * width * SIZE < parentWidth) {
scale = parentWidth / (double)SIZE / width;
} else if (scale * width * SIZE < parentHeight) {
scale = parentHeight / (double)SIZE / width;
}*/
this.repaint();
setPreferredSize(new Dimension((int)(scale * width * SIZE),
(int)(scale * width * SIZE)));
JViewport viewport = ((JViewport)(getParent().getParent().getComponent(0)));
Point orig = viewport.getViewPosition();
viewport.setViewPosition(new Point(
orig.x - (int)Math.round(loc.x*(1 - scale/prevScale)),
orig.y - (int)Math.round(loc.y*(1 - scale/prevScale))));
System.out.println(orig + "\n " + loc + "\n " + (1 - scale / prevScale));
revalidate();
}
private class MapMouseWheelListener implements MouseWheelListener {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
loc = e.getPoint();
incrementScale(e.getWheelRotation());
}
}
}
}

looks like as JViewPort#scrollRectToVisible(Rectangle r) for me works
viewport.scrollRectToVisible(new Rectangle(new Point(
orig.x - (int) Math.round(loc.x * (1 - scale / prevScale)),
orig.y - (int) Math.round(loc.y * (1 - scale / prevScale)))));
EDIT and with correct Swing repaint's rulles, then yout codeBlock must ends with revalidate(); + repaint();
setPreferredSize(new Dimension((int) (scale * width * SIZE),
(int) (scale * width * SIZE)));
JViewport viewport = ((JViewport) (getParent().getParent().getComponent(0)));
Point orig = viewport.getViewPosition();
/*viewport.setViewPosition(new Point(
orig.x - (int) Math.round(loc.x * (1 - scale / prevScale)),
orig.y - (int) Math.round(loc.y * (1 - scale / prevScale))));*/
viewport.scrollRectToVisible(new Rectangle(new Point(
orig.x - (int) Math.round(loc.x * (1 - scale / prevScale)),
orig.y - (int) Math.round(loc.y * (1 - scale / prevScale)))));
System.out.println(orig + "\n " + loc + "\n " + (1 - scale / prevScale));
revalidate();
repaint();

Related

Loops with graphics - bug in generating the graphics

I'm trying to make my code draw 10 rectangles, each one with a random position and size.
The problem is that, for some reason, it only draws one rectangle and never draws the other 9.
I'm using Math.random.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Punto1
extends JPanel {
public static void main(String[] args) {
System.out.println("Estoy en el main");
JFrame frame = new JFrame("Soy una ventana :D");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1280, 720);
frame.setVisible(true);
frame.setLocation(400, 200);
frame.setLocationRelativeTo(null);
frame.add(new Punto1());
}
public Punto1() {
}
public void paintComponent(Graphics g) {
g.setColor(Color.BLUE);
/*if(x1 != 0 && y1 != 0 && x2 != 0 && y2 !=0){
g.drawLine(x1,y1,x2,y2);
*/
rectangulo(g);
}
public void rectangulo(Graphics g) {
for (int i = 0; i < 10; i++) {
int x = (int) Math.random() * 1120 + 75;
int y = (int) Math.random() * 680 + 75;
int width = (int) Math.random() * 960 + 50;
int height = (int) Math.random() * 960 + 50;
g.setColor(Color.BLUE);
g.drawRect(x, y, width, height);
}
}
}
(int)Math.random() is truncating the value to an int, okay, but, random returns a value between 0 & 1, meaning that, any value less then 1 will be, 0, 0 x 1120 is 0 plus 75, which is 75, so you your code is drawing 10 rectangles in the same location
Two, possible solutions:
One...
Cast the result of the calculation to int after it's performed:
int x = (int)(Math.random() * 1120 + 75)
This will ensure that the calculation is done against a double base value and the truncated to int after the result is calculated
Two
Make use of the Graphics 2D API and use a Rectangle2D which supports double values...
double x = Math.random() * 1120 + 75;
double y = Math.random() * 680 + 75;
double width = Math.random() * 960 + 50;
double height = Math.random() * 960 + 50;
Rectangle2D rect = new Rectangle2D.Double(x, y, width, height);
((Graphics2D)g).draw(rect);
Side note...
Also, unless you have a specific reason to do otherwise, you should call setVisible last - it will cause less issues
JFrame frame = new JFrame("Soy una ventana :D");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1280,720);
frame.add(new Punto1());
frame.setLocationRelativeTo(null);
frame.setVisible(true);
And finally (no, really ;))
Because of the way windows work an various operating systems, the panel is unlikely to be the same size as the window, in fact, in most cases, it's smaller.
In this case you should avoid relying on magic numbers and use known values
double x = Math.random() * getWidth() + 75;
double y = Math.random() * getHeight() + 75;
double width = Math.random() * (getWidth() / 2.0) + 50;
double height = Math.random() * (getHeight() / 2.0) + 50;
Rectangle2D rect = new Rectangle2D.Double(x, y, width, height);
((Graphics2D)g).draw(rect);

Setting the circle (oval) radius to 10 with others being bigger than the first?

I want to create 12 concentric circles where the first is radius 10 pixels and the rest have different colors. I am stuck on how to change the radius, and I assume making them different colors is easy. I currently output 12 really small circles. I am not sure what is wrong.
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
public class Circles extends JComponent {
public void paintComponent(Graphics g){
int width = getWidth();
int height = getHeight();
int xPoint = width / 2;
int yPoint = height / 2;
for(int i = 5; i <= 12; i++){
g.drawOval(xPoint - (i * 5), yPoint - (i * 5), (i * 10), (i * 10));
}
}
#Override
public Dimension getPreferredSize(){
return new Dimension(400, 400);
}
public static void main(String[] args){
JFrame frame = new JFrame("Line Loop");
frame.add(new Circles());
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
I was being stupid. Here's how I fixed it.
for(int i = 0; i <= 12; i++){
g.drawOval(xPoint - (i * 50), yPoint - (i * 50), (i * 100), (i * 100));
}
}

Matrix square brackets

I'm using Java Swing and I need to display a matrix with square brackets (normal square bracket like the one we use in math that spans more than one line), the matrix size is not fixed, it depends on the input.
Here is the code I'm using to display the matrix:
public static void printMatrix(String[][] matrix) {
String output = "";
for (int x = 0; x < matrix.length; x++) {
output += Arrays.toString(matrix[x]) + "\n";
}
JOptionPane.showMessageDialog(null, output, "Matrix",
JOptionPane.INFORMATION_MESSAGE);
}
The output:
But I need to have one big connected square bracket as follows:
So I'm searching on how to do this and I found this link https://docs.oracle.com/javase/tutorial/uiswing/components/border.html but it doesn't contain the brackets that I need and also found this https://team.mumie.net/mumie/mathletfactory_lib_apidocs/net/mumie/mathletfactory/display/noc/matrix/MatrixBorder.html#MatrixBorder%28java.awt.Component,%20int%29 but I didn't find any examples on how to use it.
Based on nIcE cOw's answer on one of my above comments, you need to create your own CustomBorder class that extends AbstractBorder and override its paintBorder() method to draw each part of the brackets.
In this case I divided this task in 3 parts, the top / bottom / left & right part of both brackets.
The internalGap variable is the space that should be between the content and the border
Here are some screenshots of how the output looks like:
With 2, 6 and 10 elements
The code that produces the above outputs is:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.AbstractBorder;
public class EquationMatrixBorder {
private JPanel pane;
private CustomBorder customBorder;
private static final int ROWS_AND_COLS = 1;
private void displayGUI() {
JFrame frame = new JFrame("Custom Border Example");
customBorder = new CustomBorder(Color.RED, 15, 10);
pane = new JPanel();
pane.setLayout(new GridLayout(ROWS_AND_COLS, ROWS_AND_COLS, 15, 15));
//Used to fill the grid, not relevant to question
Random random = new Random();
for (int i = 0; i < ROWS_AND_COLS; i++) {
for (int j = 0; j < ROWS_AND_COLS; j++) {
int r = 0;
if (j % 2 == 0) {
r = random.nextInt(2);
} else {
r = random.nextInt(2) - 1;
}
pane.add(new JLabel(String.valueOf(r)));
}
}
pane.setBorder(customBorder);
frame.add(pane);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
#Override
public void run() {
new EquationMatrixBorder().displayGUI();
}
};
EventQueue.invokeLater(runnable);
}
}
class CustomBorder extends AbstractBorder {
private Color color;
private int gap;
private int bracketsTopAndBottom = 10;
private int internalGap;
public CustomBorder(Color color, int gap, int internalGap) {
this.color = color;
this.gap = gap;
this.internalGap = internalGap;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
super.paintBorder(c, g, x, y, width, height);
Graphics2D g2d = null;
if (g instanceof Graphics2D) {
g2d = (Graphics2D) g;
g2d.setColor(color);
g2d.setStroke(new BasicStroke(3));
//top part of brackets
g2d.drawLine(x + gap, y + gap, x + gap + bracketsTopAndBottom, (y + gap));
g2d.drawLine(width - x - gap - bracketsTopAndBottom, y + gap, width - gap - x, (y + gap));
//bottom part of brackets
g2d.drawLine(x + gap, height - gap, x + gap + bracketsTopAndBottom, height - gap);
g2d.drawLine(width - x - gap - bracketsTopAndBottom, height - gap, width - gap - x, height - gap);
//left and right part of brackets
g2d.drawLine(x + gap, y + gap, x + gap, height - gap);
g2d.drawLine(width - x - gap, y + gap, width - x - gap, height - gap);
}
}
#Override
public Insets getBorderInsets(Component c) {
return getBorderInsets(c, new Insets(gap, gap, gap, gap));
}
#Override
public Insets getBorderInsets(Component c, Insets insets) {
insets.left = insets.top = insets.right = insets.bottom = gap + internalGap;
return insets;
}
}
Note
I haven't done rows and cols numbers shown in desired output of OP, I'm leaving that out as this question is only related to the square brackets

How can I move my panels from (0,0) coordinates

as you see, 5x5 window is contiguous to 0,0 coordinates. I want to move them like 10,10. How can I do it ?
I think the problem is in my BorderLayout. North and East is freee. How can i fill them with 10,10 dimension
import java.awt.*;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
SOS game = new SOS(5);
// JPanel for sos template
SOSCanvas window = new SOSCanvas(game);
// JPanel which includes info panel, time and exit panel
SOSGUIPanel info = new SOSGUIPanel(window, "Cihangir", "Mercan");
JFrame jf = new JFrame();
jf.setTitle( "SOS game");
jf.setSize(340, 450);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
jf.setLayout( new BorderLayout() );
jf.add(window, BorderLayout.CENTER );
jf.add(info, BorderLayout.SOUTH );
}
}
The grid is provided with this class;
import java.awt.*;
import javax.swing.*;
public class SOSCanvas extends JPanel {
// PROPERTIES
protected SOS game;
// CONSTRUCTORS
public SOSCanvas( SOS game )
{
this.game= game;
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
int dimension = game.getDimension();
g.drawRect (0, 0, 300, 300);
// drawing horizontal lines
for ( int i = 1; i < dimension; i++ )
{
g.drawLine(0, i * (300 / dimension), 300, i * (300 / dimension) );
}
// drawing vertical lines
for ( int i = 1; i < dimension; i++ )
{
g.drawLine( i * (300 / dimension), 0, i * (300 / dimension), 300);
}
}
}
The first thing you need to do, is stop relying on magic numbers, you need to know the current size of the component.
Instead of using g.drawRect (0, 0, 300, 300);, you need to get the component's current width and height. From this you can make decisions about how you might handle the differences in your expectations and the actual width and height of the component.
For example, the following is a scalable grid, which can grow and shrink based on the available space, but will always remain square. It will always attempt to center itself within the available space as well...
public class SOSCanvas extends JPanel {
public SOSCanvas() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int dimension = 5;//game.getDimension();
int width = getWidth() - 1;
int height = getHeight() - 1;
int cellSize = Math.min(width, height);
int xOffset = (width - cellSize) / 2;
int yOffset = (height - cellSize) / 2;
g.drawRect(xOffset, yOffset, cellSize, cellSize);
// drawing horizontal lines
for (int i = 1; i < dimension; i++) {
g.drawLine(
xOffset,
yOffset + (i * (cellSize / dimension)),
xOffset + cellSize,
yOffset + (i * (cellSize / dimension)));
}
// drawing vertical lines
for (int i = 1; i < dimension; i++) {
g.drawLine(
xOffset + (i * (cellSize / dimension)),
yOffset,
xOffset + (i * (cellSize / dimension)),
yOffset + cellSize);
}
}
}
Even if you wanted a static grid of (lets say) 300x300, the x offset you would need to center the grid horizontally would be calculated using something like xOffset = (getWidth() - 300) / 2, then all you horizontal drawing would need to start from this offset...

Resize Graphics2d into JScrollPane

In connection with question Resizing a component without repainting is my question how to create resiziable custom Graphics2d in form
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ZoomWithSelectionInViewport implements MouseWheelListener {
private JComponent b;
private int hexSize = 3;
private int zoom = 80;
private JScrollPane view;
public ZoomWithSelectionInViewport() throws Exception {
b = new JComponent() {
private static final long serialVersionUID = 1L;
#Override
public Dimension getPreferredSize() {
return new Dimension(700, 700);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = ((Graphics2D) g);
int vertOffsetX, vertOffsetY, horizOffsetX, horizOffsetY;
vertOffsetX = (int) ((double) hexSize * Math.sqrt(3.0f));
vertOffsetY = (int) ((double) -hexSize - 1 * Math.sqrt(3.0f) / 2.0f);
horizOffsetX = (int) ((double) hexSize * Math.sqrt(3.0f));
horizOffsetY = (int) ((double) hexSize + 1 * Math.sqrt(3.0f) / 2.0f);
for (int x = 0; x < 50; x++) {
for (int y = 0; y < 50; y++) {
int[] xcoords = new int[6];
int[] ycoords = new int[6];
for (int i = 0; i < 6; i++) {
xcoords[i] = (int) ((hexSize + x * horizOffsetX + y * vertOffsetX)
+ (double) hexSize * Math.cos(i * 2 * Math.PI / 6));
ycoords[i] = (int) (((getSize().height / 2) + x * horizOffsetY
+ y * vertOffsetY) + (double) hexSize * Math.sin(i * 2 * Math.PI / 6));
}
g2d.setStroke(new BasicStroke(hexSize / 2.5f));
g2d.setColor(Color.GRAY);
g2d.drawPolygon(xcoords, ycoords, 6);
}
}
}
};
view = new JScrollPane(b);
b.addMouseWheelListener(this);
JFrame f = new JFrame();
f.setLocation(10, 10);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(view);
f.setPreferredSize(b.getPreferredSize());
f.pack();
f.setVisible(true);
}
public void mouseWheelMoved(MouseWheelEvent e) {
zoom = 100 * -Integer.signum(e.getWheelRotation());
if (hexSize - Integer.signum(e.getWheelRotation()) > 0) {
hexSize -= Integer.signum(e.getWheelRotation());
}
Dimension targetSize = new Dimension(b.getWidth() + zoom, b.getHeight() + zoom);
b.setPreferredSize(targetSize);
b.setSize(targetSize);
b.revalidate();
b.repaint();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
ZoomWithSelectionInViewport example = new ZoomWithSelectionInViewport();
} catch (Exception ex) {
//
}
}
});
}
}
If I understand correctly, you want the scroll pane's scroll bars to reflect the current zoom state. I see two alternatives:
Don't override getPreferredSize() in the component, and adjust the preferred size in the mouse listener to include the zoomed image; it appears slightly truncated on the right.
Do override getPreferredSize() in the component, and adjust the returned Dimension (now a constant) to include the zoomed boundary implicit in paintComponent().
I'd prefer the latter. I've also found it helpful to write explicit transformation functions to convert zoomed and un-zoomed coordinates, as shown here. An inverse AffineTransform, shown here, is also possible.

Categories