JFrame wrong location with Ubuntu (Unity ?) - java

It seems that there is a bug with Ubuntu (maybe only unity). The decoration of the JFrame is taken into account for getLocation() and getSize(), but not for setLocation() and setSize(). This leads to weird behaviour. For instance, if you use pack() after the frame is displayed and the dimensions changed, the frame will go down 20 pixels...
To illustrate a concrete case when it becomes really annoying, I made a SSCCE. It's a JFrame with a basic JPanel. If you drag the panel, the JFrame is supposed to move along.
Under Windows, it works as expected. Under Ubuntu, if I do setUndecorated(true) it will also work fine, but if I let the decoration, the JFrame turn crazy !
public class Test {
private static JFrame mainFrame;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
mainFrame = new JFrame("test");
mainFrame.setSize(300,20);
mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
mainFrame.setVisible(true);
Container pane = mainFrame.getContentPane();
pane.addMouseMotionListener(new MouseMotionListener() {
private int posX = 0, posY = 0;
#Override
public void mouseDragged(MouseEvent e) {
int x = e.getX() - posX + mainFrame.getX();
int y = e.getY() - posY + mainFrame.getY();
mainFrame.setLocation(x, y);
}
#Override
public void mouseMoved(MouseEvent e) {
posX = e.getX();
posY = e.getY();
}
});
}
});
}
}
I don't know how I can fix that. How can I get the size of the windows decoration ? And I have no idea about which versions of Ubuntu are concerned. And if it is only a Unity problem, I don't even know how to find out if my user is using Unity...
Any idea for a workaround ?
Edit :
Ok, MadProgrammer did provide a better code, but the bug still occurs sometimes. I edited my MouseListener accordingly to track the bug :
pane.addMouseMotionListener(new MouseMotionListener() {
private int posX = 0, posY = 0;
#Override
public void mouseDragged(MouseEvent e) {
int x = e.getXOnScreen() - posX;
int y = e.getYOnScreen() - posY;
mainFrame.setLocation(x, y);
System.out.println("drag : border ignored / border considered : "+(mainFrame.getY()+e.getY())+" / "+e.getYOnScreen());
}
#Override
public void mouseMoved(MouseEvent e) {
posX = e.getXOnScreen() - mainFrame.getX();
posY = e.getYOnScreen() - mainFrame.getY();
System.out.println("move : border ignored / border considered : "+e.getY()+" / "+posY);
}
});
Each time that the 2 values are identical, it means that the bug will occur on the next click. Otherwise, the values are different. On other OS, the values are always the same. Actually, they should be or the same always, or always different. I don't understand how they can be sometimes equal and sometimes different...

I don't have Ubuntu to test with, but I've used something similar to this on both MacOS and Windows
import java.awt.Container;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test {
private static JFrame mainFrame;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
mainFrame = new JFrame("test");
mainFrame.setSize(300, 100);
mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
mainFrame.setVisible(true);
Container pane = mainFrame.getContentPane();
MouseAdapter ma = new MouseAdapter() {
private Point offset;
#Override
public void mouseDragged(MouseEvent e) {
if (offset != null) {
Point pos = e.getLocationOnScreen();
int x = pos.x - offset.x;
int y = pos.y - offset.y;
System.out.println(x + "x" + y);
SwingUtilities.getWindowAncestor(e.getComponent()).setLocation(x, y);
}
}
#Override
public void mousePressed(MouseEvent e) {
Point pos = SwingUtilities.getWindowAncestor(e.getComponent()).getLocation();
// Point pos = e.getComponent().getLocationOnScreen();
offset = new Point(e.getLocationOnScreen());
System.out.println(pos + "/" + offset);
offset.x -= pos.x;
offset.y -= pos.y;
System.out.println(offset);
}
};
pane.addMouseListener(ma);
pane.addMouseMotionListener(ma);
}
});
}
}
This should work for both decorated and undecorated windows, as it takes the difference between the positions of the component (on the screen) and the windows current position. When dragged, it calculates the distance of movement from the click point and updates the window's location accordingly (allowing for the original offset of the click)

Related

How is it possible to make an image follow the framesize in Java? [duplicate]

When a user clicks on the corner of a JFrame to resize and drags the mouse around, the JFrame redraws based on the current position of the mouse as the user drags. How can you listen to these events?
Below is the what I have currently tried:
public final class TestFrame extends JFrame {
public TestFrame() {
this.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
// This is only called when the user releases the mouse button.
System.out.println("componentResized");
}
});
}
// These methods do not appear to be called at all when a JFrame
// is being resized.
#Override
public void setSize(int width, int height) {
System.out.println("setSize");
}
#Override
public void setBounds(Rectangle r) {
System.out.println("setBounds A");
}
#Override
public void setBounds(int x, int y, int width, int height) {
System.out.println("setBounds B");
}
}
How can I determine and constrain how the user resizes a window (based on the current aspect ratio of the window) as they are dragging around the mouse around?
You can add a component listener and implement the componentResized function like that:
JFrame component = new JFrame("My Frame");
component.addComponentListener(new ComponentAdapter()
{
public void componentResized(ComponentEvent evt) {
Component c = (Component)evt.getSource();
//........
}
});
EDIT: Apparently, for JFrame, the componentResized event is hooked to the mouseReleased event. That's why the method is invoked when the mouse button is released.
One way to achieve what you want, is to add a JPanel that will cover the whole area of your JFrame. Then add the componentListener to the JPanel (componentResized for JPanel is called even while your mouse is still dragging). When your frame is resized, your panel will also be resized too.
I know, this isn't the most elegant solution, but it works!
Another workaround (which is very similar to Alex's but a little more straightforward) is to listen to the events from the JFrame's root pane instead:
public final class TestFrame extends JFrame {
public TestFrame() {
this.getRootPane().addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
// This is only called when the user releases the mouse button.
System.out.println("componentResized");
}
});
}
}
Depending on other implementation details, it's possible that the root pane might be changed. If that's a possibility then you could override setRootPane() and deal with that. Since setRootPane() is protected and only called from the constructor though, it's unlikely you'll need to do that.
You probably need to override something like validate (don't forget to call the super). Of course, that still may not work if you are using a windowing system to configured to drag outlines.
public class MouseDrag extends Component implements MouseListener,
MouseMotionListener {
/** The Image we are to paint */
Image curImage;
/** Kludge for showStatus */
static Label status;
/** true if we are in drag */
boolean inDrag = false;
/** starting location of a drag */
int startX = -1, startY = -1;
/** current location of a drag */
int curX = -1, curY = -1;
// "main" method
public static void main(String[] av) {
JFrame f = new JFrame("Mouse Dragger");
Container cp = f.getContentPane();
if (av.length < 1) {
System.err.println("Usage: MouseDrag imagefile");
System.exit(1);
}
Image im = Toolkit.getDefaultToolkit().getImage(av[0]);
// create a MouseDrag object
MouseDrag j = new MouseDrag(im);
cp.setLayout(new BorderLayout());
cp.add(BorderLayout.NORTH, new Label(
"Hello, and welcome to the world of Java"));
cp.add(BorderLayout.CENTER, j);
cp.add(BorderLayout.SOUTH, status = new Label());
status.setSize(f.getSize().width, status.getSize().height);
f.pack();
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
// "Constructor" - creates the object
public MouseDrag(Image i) {
super();
curImage = i;
setSize(300, 200);
addMouseListener(this);
addMouseMotionListener(this);
}
public void showStatus(String s) {
status.setText(s);
}
// Five methods from MouseListener:
/** Called when the mouse has been clicked on a component. */
public void mouseClicked(MouseEvent e) {
}
/** Called when the mouse enters a component. */
public void mouseEntered(MouseEvent e) {
}
/** Called when the mouse exits a component. */
public void mouseExited(MouseEvent e) {
}
// And two methods from MouseMotionListener:
public void mouseDragged(MouseEvent e) {
Point p = e.getPoint();
// System.err.println("mouse drag to " + p);
showStatus("mouse Dragged to " + p);
curX = p.x;
curY = p.y;
if (inDrag) {
repaint();
}
}
public void paint(Graphics g) {
int w = curX - startX, h = curY - startY;
Dimension d = getSize();
g.drawImage(curImage, 0, 0, d.width, d.height, this);
if (startX < 0 || startY < 0)
return;
System.err.println("paint:drawRect #[" + startX + "," + startY
+ "] size " + w + "x" + h);
g.setColor(Color.red);
g.fillRect(startX, startY, w, h);
}
}

JPanel subclass "jumps around" when dragged

I'm currently coding on a small "Paint"-program of mine; so far, you can draw on it with a pen, zoom in up to 100x, and choose a color. Next thing I wanted to add was (or is) the possibility to drag the JPanel subclass, on which the image chosen to edit is drawn, around. Basically, by holding down the right mouse button, you change the location of the JPanel subclass, which is located on a JInternalFrame. I've got a code sample that should be working fine on its own; at least it does for me. To replicate the issue, just launch the DragPanelTest class and drag your mouse while over the component with the red border - the panel isn't dragged smoothly, but instead jumps back and forth all the time.
code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.BorderFactory;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class DragPanelTest extends JPanel implements MouseMotionListener, MouseListener {
Point oldDragPt;
boolean dragEnabled;
int newX;
int newY;
public static void main(String[] args) {
System.out.println("Launching Test.java ...");
JFrame frame = new JFrame("Title");
JInternalFrame intFrame = new JInternalFrame("title", true, true, true, true);
JDesktopPane deskPane = new JDesktopPane();
DragPanelTest dragPanel = new DragPanelTest();
frame.setSize(300, 300);;
frame.setLayout(null);
frame.setVisible(true);
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int x = (d.width - frame.getWidth()) / 2;
int y = (d.height - frame.getHeight()) / 2;
frame.setLocation(x, y);
deskPane.setBounds(50, 50, 200, 200);
deskPane.setBorder(BorderFactory.createLineBorder(Color.BLACK));
frame.add(deskPane);
deskPane.add(intFrame);
intFrame.setSize(150, 150);
intFrame.add(dragPanel);
intFrame.setVisible(true);
}
public DragPanelTest() {
this.setSize(100,100);
this.setBorder(BorderFactory.createLineBorder(Color.RED));
this.addMouseListener(this);
this.addMouseMotionListener(this);
}
public void mousePressed(MouseEvent e) {
if (e.isMetaDown()) {
dragEnabled = true;
oldDragPt = new Point((int) this.getMousePosition().getX(), (int) this.getMousePosition().getY());
}
repaint();
}
public void mouseReleased(MouseEvent e) {
dragEnabled = false;
oldDragPt = null;
}
public void mouseDragged(MouseEvent e) {
if (e.isMetaDown() && this.getMousePosition() != null) {
if (oldDragPt != null) {
if (dragEnabled) {
int mouseX = (int) this.getMousePosition().getX();
int mouseY = (int) this.getMousePosition().getY();
int x = this.getX();
int y = this.getY();
int diffX = (mouseX - (int) oldDragPt.getX());
int diffY = (mouseY - (int) oldDragPt.getY());
newX = getX() + diffX;
newY = getY() + diffY;
this.setLocation(newX, newY);
this.repaint();
oldDragPt = new Point(mouseX, mouseY);
}
} else {
oldDragPt = new Point((int) this.getMousePosition().getX(), (int) this.getMousePosition().getY());
}
}
}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
}
Note that I left out some System.out.println() or #Override commands to shorten the code a bit.
Anyhow, if anyone knows what I'm doing wrong / what to improve, I'd be very thankful!
the panel isn't dragged smoothly, but instead jumps back and forth all the time.
When you have jumping its because the relative movements between the mouse and the component and the parent panel is off. Your code is too complicated to point out the exact line causing the problem.
The basic code for dragging a component around a panel is:
public class DragListener extends MouseInputAdapter
{
Point location;
MouseEvent pressed;
public void mousePressed(MouseEvent me)
{
pressed = me;
}
public void mouseDragged(MouseEvent me)
{
Component component = me.getComponent();
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
}
For more advanced code with more feature you can also check out the Component Mover class found in Moving Windows.
I had a similar problem with moving the viewport for a scrollpane. I think that the when a component is re-positioned, the value of the saved point is relative to the original location and is incorrect relative to the new location. My solution, which seems illogical to me, made the drag operation flow smoothly. What I did is to add the point delta back into the saved location.
This is the basic code to drag the viewport:
Point priorPanPoint;
public void mouseDragged(MouseEvent e) {
Point newPt = subtractPoints(priorPanPoint, e.getPoint());
priorPanPoint = addPoints(e.getPoint(), newPt);
newPt = addPoints(getVisibleRect().getLocation(), newPt);
viewport.setViewPosition(newPt);
}
public void mousePressed(MouseEvent e) {
priorPanPoint = e.getPoint();
}
private Point addPoints(Point p1, Point p2) {
return new Point(p1.x + p2.x, p1.y + p2.y); }
private Point subtractPoints(Point p1, Point p2) {
return new Point(p1.x - p2.x, p1.y - p2.y);
}

Java Simple Bouncing Ball, SingleThread, Mouse Listener, JFrame

I've created a Bouncing Ball application in in Java. The goal is to have a ball appear on mousePressed() and have it bounce off the walls without leaving the frame. Only one Ball One Thread, should be easy.. My problem is that every time I click to make the ball appear it gets faster and I have no idea why. Can somebody help me please. PS: I'm new to threads.
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MyFrame extends JPanel {
public int xPos, yPos, xDir = 3, yDir = 4;
public int diameter = 50;
public MyFrame(){
final JFrame thisFrame = new JFrame();
thisFrame.add(this);
thisFrame.setTitle("Bouncing Ball");
thisFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
thisFrame.setLocationRelativeTo(null);
thisFrame.setSize(500, 500);
this.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {
xPos = e.getX();
yPos = e.getY();
Thread t = new Thread() {
#Override
public void run() {
while(true){
try{
Thread.sleep(10);
}catch(Exception e){};
xPos += xDir;
yPos += yDir;
if(xPos + diameter >= thisFrame.getWidth() - 25 || xPos <= 0) xDir = -xDir;
if(yPos + diameter >= thisFrame.getHeight() - diameter || yPos <= 0) yDir = -yDir;
repaint();
}
}
};
t.start();
}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseClicked(MouseEvent e) {}
});
thisFrame.setVisible(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillOval(xPos, yPos, diameter, diameter);
}
}
public class MyMain{
public static void main(String[] args) {
new MyFrame();
}
}
Every time you click, you are starting a new Thread, which means you have another thread updating the x/y positions.
For example, 1 thread means your update the x/p once per sync, 2 means you're updating the x/y position at least twice per cycle and this just gets compounded each time you add a new thread.
A better solution would be to start the Thread at some time earlier and then use a List to maintain the position and direction of the ball.
This will require a little but synchronisation to keep things safe
FYI a delay of 40 milliseconds is roughly 25 fps, 16 milliseconds is roughly 60 fps. IMHO, for what you are doing, 10 milliseconds seems excessive...

Resizing a JFrame and maintain aspect ratio?

I want the user to resize the window while maintaining the same aspect ratio (rectangle in which the width > height. In other words, if height is changed, I want to force width to change by a larger amount, and vice versa. Preferably, I want the resize to happen while the window is being resized, not after. The code below isn't working for me at all.. and whenever I try to resize the window it just reverts back to its original size. Even when I drag to resize, it doesn't change both width AND height. Any help would be appreciated thanks!
public void componentResized(ComponentEvent arg0)
{
int setHeight = arg0.getComponent().getHeight();
int setWidth = arg0.getComponent().getWidth();
double newWidth = 0;
double newHeight = 0;
{
if(setHeight != oldHeight)
{
heightChanged = true;
}
if(setWidth != oldWidth)
{
widthChanged = true;
}
}
{
if(widthChanged == true && heightChanged == false)
{
newWidth = setWidth;
newHeight = setWidth*HEIGHT_RATIO;
}
else if(widthChanged == false && heightChanged == true)
{
newWidth = setHeight * WIDTH_RATIO;
newHeight = setHeight;
}
else if(widthChanged == true && heightChanged == true)
{
newWidth = setWidth;
newHeight = setWidth*HEIGHT_RATIO;
}
}
int x1 = (int) newWidth;
int y1 = (int) newHeight;
System.out.println("W: " + x1 + " H: " + y1);
Rectangle r = arg0.getComponent().getBounds();
arg0.getComponent().setBounds(r.x, r.y, x1, y1);
widthChanged = false;
heightChanged = false;
oldWidth = x1;
oldHeight = y1;
}
There is no easy way to achieve that using Java (AWT limitiation).
Possible solutions:
correct window size after the user stopped resizing (annoying from the users pov)
use a unresizable JWindow/JDialog without any decorations and re-implement window resizing programmatically. Cursor shapes etc. is avaiable with Swing/AWT. I have done this in a commercial project (so no source :-) ). Only problem with that: it is quite volatile regarding platforms/jdk versions (though AWT/Swing is not changed that much in recent years).
manipulating the window size while user is dragging/resizing will not work properly
I figured out a way to keep a JPanel's aspect ratio as a square for a school project. It took a few different elements in different places, so I hope I can give enough information to help you implement the method into your own project using whatever aspect ratio you want. I was able to boil it down to two java files. Not sure how to condense it any further
the first: AspectRatio.java
import java.awt.Color;
import javax.swing.JFrame;
public class AspectRatio {
public static void main(String[] args) {
JFrame frame = new JFrame("Stay a square");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBackground(Color.WHITE);
frame.getContentPane().add(new SquarePanel());
frame.pack();
frame.setVisible(true);
}
}
the second has the meat of it: SquarePanel.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class SquarePanel extends JPanel {
//Fields
private JPanel square;
private int panelHeight;
private int panelWidth;
//Constructor
public SquarePanel() {
square = new JPanel();
square.setBackground(Color.BLACK);
square.setPreferredSize(new Dimension(800,800));
this.addComponentListener(new FrameSizeListener());
this.add(square);
}
//resizing method
public void resizeSquare() {
panelHeight = super.getHeight();
panelWidth = super.getWidth();
if(panelHeight>(panelWidth)) {
square.setPreferredSize(new Dimension(panelWidth,panelWidth));
}
else if (panelHeight<(panelWidth)) {
square.setPreferredSize(new Dimension(panelHeight,panelHeight));
}
}
//Window resizing listener
private class FrameSizeListener implements ComponentListener{
public void componentResized(ComponentEvent e) {
resizeSquare();
revalidate();
}
public void componentMoved(ComponentEvent e) {
}
public void componentShown(ComponentEvent e) {
}
public void componentHidden(ComponentEvent e) {
}
}
}
Hope that helps. The code as is will just make a black square on a white background, but the square stays a square and stays as big as it can.

Using the coordinate plane in the JFrame

//I am trying to learn how to draw objects in java. I'm getting better at it, but once I get an image on the screen I am having trouble manipulating it. The numbers I put in don't make sense to how the shapes are turning out. At least to me they don't. In algebra if you increase a number on the x axis it goes to the right and if you increase a number on the y axis it goes up. Thats not whats happening here. Can anyone explain to me how this works? I'm still new to java, so the more explanation and detail the better. I'm trying to take a couple of hours out a day over my summer to learn java and sometimes it gets a little frustrating. Any help is greatly appreciated.
Here the Co-ordinates start from the TOP LEFT SIDE of the screen, as as you increase value of X, you will move towards RIGHT SIDE, though as you increase the value of Y, you will move DOWNWARDS. Here is a small example Program for you to understand this a bit better, simply click on it anywhere.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DrawingExample
{
private int x;
private int y;
private String text;
private DrawingBase canvas;
private void displayGUI()
{
JFrame frame = new JFrame("Drawing Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
canvas = new DrawingBase();
canvas.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent me)
{
text = "X : " + me.getX() + " Y : " + me.getY();
x = me.getX();
y = me.getY();
canvas.setValues(text, x, y);
}
});
frame.setContentPane(canvas);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new DrawingExample().displayGUI();
}
});
}
}
class DrawingBase extends JPanel
{
private String clickedAt = "";
private int x = 0;
private int y = 0;
public void setValues(String text, int x, int y)
{
clickedAt = text;
this.x = x;
this.y = y;
repaint();
}
public Dimension getPreferredSize()
{
return (new Dimension(500, 400));
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawString(clickedAt, x, y);
}
}

Categories