tl;dr
Under Windows 10, if I put my secondary display to the right of the primary one, and apply a scaling (e.g. 150%) to the secondary, then the display coordinates (as returned by the Java API) overlap instead of letting the display bounds sit side by side. In other words, if I slowly move my mouse from the left edge of the primary to the right edge of the secondary, Java's API MouseInfo.getPointerInfo().getLocation() returns an increasing X-position from 0 to 1920, then once the cursor enters the second screen, the value jumps back down to 1280 and then increases again to 2560. So the 1280-1920 range is returned twice, for different areas.
At the end of the post, I have included an (updated) demo that makes the issue obvious. Don't hesitate to try it and report back.
The long version:
This text gives (too) much context but is also meant to share the things I learned while searching on the topic.
First, why bother ? Because I am building a screen capture application in Java that requires a correct handling of multi-display configurations, including displays where Windows' scaling feature is applied.
Using the Java API (GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()), as long as the scaling is 100%, one can observe that the primary display has its top left corner at the origin (0,0), with the other displays having coordinates "next" to the main one.
The following pictures were made using the code at the end of the post.
E.g. if we have 2 full-hd displays, the main one has its top left corner is at (0,0), while...
if the secondary is positioned at its right, at the same level, its top left corner is (1920,0):
if the secondary is positioned at its left, at the same level, its top left corner is (-1920,0):
if the secondary is positioned below, aligned horizontally, its top left corner is (0,1080):
if the secondary is positioned above, aligned horizontally, its top left corner is (0,-1080):
and so on if the displays are not aligned:
or with different resolutions:
However, if the secondary display is scaled, things go awry: it seems the scaling factor is applied not only to its dimensions, but also its origin, which gets closer to (0,0).
If the secondary is on the left, it makes sense. For example, when the secondary 1920x1080 is scaled at 150%, it makes a logical 1280x720 positioned at (-1280,0):
But if the secondary is on the right, the origin is also scaled to (1280,0), getting closer to the origin and causing it to "overlap" the primary one:
In other words, if the mouse is at (1800,0) - see red dot above - I see no way of knowing if it actually is positioned on the right of the first display (at 120px from the right edge) or on the left of the secondary one (at 520px of the left edge). When moving the mouse from the primary to the secondary display in this case, the X position of the mouse "jumps back" when it reaches the border of the primary display.
The same is true for positioning a window on the screens. If I set the X-position of a dialog to 1800, I have no way to know where it will open.
After much browsing, some answers like this one indicate that the only way to query Windows scaling is by using native calls. Indeed, using JNA, one can get the physical size of the displays (although the answer seems to indicate that call should return the logical size). I.e the JNA calls ignore the scaling factor, and behaves exactly like the Java API when scaling is at 100%:
So am I missing something ?
Not knowing the scaling factor is a small issue, but not being able to tell which display the mouse is over, or not being able to position a window on the display I want looks like a real problem to me. Is it a Java Bug ?
Note: Here is the code for the app used above, run on with OpenJDK14 on Windows 10 64b. It shows a scaled down version of your display setup and mouse position as perceived by Java. It can also place and move a small dialog across the real screens if you click and drag inside the small rectangles. Credit: The UI is inspired by the WheresMyMouse code posted here.
As is, the code uses only the Java API.
If you want to compare with JNA, search for the 4 blocks marked "JNA_ONLY", uncomment them, and add the jna libs. The demo will then toggle between JNA and Java API for displaying screen bounds and mouse cursor at each right-click. The dialog positioning never uses JNA in this version.
// JNA_ONLY
//import com.sun.jna.platform.win32.User32;
//import com.sun.jna.platform.win32.WinDef;
//import com.sun.jna.platform.win32.WinUser;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
/**
* Java multi-display detection and analysis.
* UI idea based on WheresMyMouse - https://stackoverflow.com/a/21592711/13551878
*/
public class ShowDisplays {
private static boolean useJna = false;
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame("Display Configuration");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public static class TestPane extends JPanel {
private List<Rectangle> screenBounds;
JDialog dlg;
public TestPane() {
screenBounds = getScreenBounds();
// refresh screen details every second to reflect changes in Windows Preferences in "real time"
new Timer(1000, e -> screenBounds = getScreenBounds()).start();
// Refresh mouse position at 25fps
new Timer(40, e -> repaint()).start();
MouseAdapter mouseAdapter = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
useJna = !useJna;
repaint();
}
}
#Override
public void mousePressed(MouseEvent e) {
System.out.println(e.getButton());
if (e.getButton() == MouseEvent.BUTTON1) {
if (!dlg.isVisible()) {
dlg.setVisible(true);
}
moveDialogTo(e.getPoint());
}
}
#Override
public void mouseDragged(MouseEvent e) {
moveDialogTo(e.getPoint());
}
private void moveDialogTo(Point mouseLocation) {
final Rectangle surroundingRectangle = getSurroundingRectangle(screenBounds);
double scaleFactor = Math.min((double) getWidth() / surroundingRectangle.width, (double) getHeight() / surroundingRectangle.height);
int xOffset = (getWidth() - (int) (surroundingRectangle.width * scaleFactor)) / 2;
int yOffset = (getHeight() - (int) (surroundingRectangle.height * scaleFactor)) / 2;
int screenX = surroundingRectangle.x + (int) ((mouseLocation.x - xOffset) / scaleFactor);
int screenY = surroundingRectangle.y + (int) ((mouseLocation.y - yOffset) / scaleFactor);
dlg.setLocation(screenX - dlg.getWidth() / 2, screenY - dlg.getHeight() / 2);
}
};
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
// Prepare the test dialog
dlg = new JDialog();
dlg.setTitle("Here");
dlg.setSize(50, 50);
dlg.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// Mouse position
Point mousePoint = getMouseLocation();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
final Rectangle surroundingRectangle = getSurroundingRectangle(screenBounds);
double scaleFactor = Math.min((double) getWidth() / surroundingRectangle.width, (double) getHeight() / surroundingRectangle.height);
int xOffset = (getWidth() - (int) (surroundingRectangle.width * scaleFactor)) / 2;
int yOffset = (getHeight() - (int) (surroundingRectangle.height * scaleFactor)) / 2;
g2d.setColor(Color.BLUE);
g2d.fillRect(xOffset, yOffset, (int) (surroundingRectangle.width * scaleFactor), (int) (surroundingRectangle.height * scaleFactor));
Font defaultFont = g2d.getFont();
for (int screenIndex = 0; screenIndex < screenBounds.size(); screenIndex++) {
Rectangle screen = screenBounds.get(screenIndex);
Rectangle scaledRectangle = new Rectangle(
xOffset + (int) ((screen.x - surroundingRectangle.x) * scaleFactor),
yOffset + (int) ((screen.y - surroundingRectangle.y) * scaleFactor),
(int) (screen.width * scaleFactor),
(int) (screen.height * scaleFactor));
// System.out.println(screen + " x " + scaleFactor + " -> " + scaledRectangle);
g2d.setColor(Color.DARK_GRAY);
g2d.fill(scaledRectangle);
g2d.setColor(Color.GRAY);
g2d.draw(scaledRectangle);
// Screen text details
g2d.setColor(Color.WHITE);
// Display number
final Font largeFont = new Font(defaultFont.getName(), defaultFont.getStyle(), (int) (screen.height * scaleFactor) / 2);
g2d.setFont(largeFont);
String label = String.valueOf(screenIndex + 1);
FontRenderContext frc = g2d.getFontRenderContext();
TextLayout layout = new TextLayout(label, largeFont, frc);
Rectangle2D bounds = layout.getBounds();
g2d.setColor(Color.WHITE);
g2d.drawString(
label,
(int) (scaledRectangle.x + (scaledRectangle.width - bounds.getWidth()) / 2),
(int) (scaledRectangle.y + (scaledRectangle.height + bounds.getHeight()) / 2)
);
// Resolution + corner
final Font smallFont = new Font(defaultFont.getName(), defaultFont.getStyle(), (int) (screen.height * scaleFactor) / 10);
g2d.setFont(smallFont);
// Resolution
String resolution = screen.width + "x" + screen.height;
layout = new TextLayout(resolution, smallFont, frc);
bounds = layout.getBounds();
g2d.drawString(
resolution,
(int) (scaledRectangle.x + (scaledRectangle.width - bounds.getWidth()) / 2),
(int) (scaledRectangle.y + scaledRectangle.height - bounds.getHeight())
);
// Corner
String corner = "(" + screen.x + "," + screen.y + ")";
g2d.drawString(
corner,
scaledRectangle.x,
(int) (scaledRectangle.y + bounds.getHeight() * 1.5)
);
}
g2d.setFont(defaultFont);
FontMetrics fm = g2d.getFontMetrics();
if (mousePoint != null) {
g2d.fillOval(xOffset + (int) ((mousePoint.x - surroundingRectangle.x) * scaleFactor) - 2,
yOffset + (int) ((mousePoint.y - surroundingRectangle.y) * scaleFactor) - 2,
4,
4
);
g2d.drawString("Mouse pointer is at (" + mousePoint.x + "," + mousePoint.y + ")", 4, fm.getHeight());
}
g2d.drawString("Click and drag in this area to move a dialog on the actual screens", 4, fm.getHeight() * 2);
// JNA_ONLY
// g2d.drawString("Now using " + (useJna ? "JNA" : "Java API") + ". Right-click to toggle", 4, fm.getHeight() * 3);
g2d.dispose();
}
}
public static Rectangle getSurroundingRectangle(List<Rectangle> screenRectangles) {
Rectangle surroundingBounds = null;
for (Rectangle screenBound : screenRectangles) {
if (surroundingBounds == null) {
surroundingBounds = new Rectangle(screenRectangles.get(0));
}
else {
surroundingBounds.add(screenBound);
}
}
return surroundingBounds;
}
private static Point getMouseLocation() {
// JNA_ONLY
// if (useJna) {
// final WinDef.POINT point = new WinDef.POINT();
// if (User32.INSTANCE.GetCursorPos(point)) {
// return new Point(point.x, point.y);
// }
// else {
// return null;
// }
// }
return MouseInfo.getPointerInfo().getLocation();
}
public static List<Rectangle> getScreenBounds() {
List<Rectangle> screenBounds;
// JNA_ONLY
// if (useJna) {
// screenBounds = new ArrayList<>();
// // Enumerate all monitors, and call a code block for each of them
// // See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
// // See http://www.pinvoke.net/default.aspx/user32/EnumDisplayMonitors.html
// User32.INSTANCE.EnumDisplayMonitors(
// null, // => the virtual screen that encompasses all the displays on the desktop.
// null, // => don't clip the region
// (hmonitor, hdc, rect, lparam) -> {
// // For each found monitor, get more information
// // See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
// // See http://www.pinvoke.net/default.aspx/user32/GetMonitorInfo.html
// WinUser.MONITORINFOEX monitorInfoEx = new WinUser.MONITORINFOEX();
// User32.INSTANCE.GetMonitorInfo(hmonitor, monitorInfoEx);
// // Retrieve its coordinates
// final WinDef.RECT rcMonitor = monitorInfoEx.rcMonitor;
// // And convert them to a Java rectangle, to be added to the list of monitors
// screenBounds.add(new Rectangle(rcMonitor.left, rcMonitor.top, rcMonitor.right - rcMonitor.left, rcMonitor.bottom - rcMonitor.top));
// // Then return "true" to continue enumeration
// return 1;
// },
// null // => No additional info to pass as lparam to the callback
// );
// return screenBounds;
// }
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] screenDevices = graphicsEnvironment.getScreenDevices();
screenBounds = new ArrayList<>(screenDevices.length);
for (GraphicsDevice screenDevice : screenDevices) {
GraphicsConfiguration configuration = screenDevice.getDefaultConfiguration();
screenBounds.add(configuration.getBounds());
}
return screenBounds;
}
}
This looks like you've run into a manifestation of bug JDK-8211999:
In a multi-monitor setting involving one HiDPI screen placed to the right of one regular monitor, on Windows 10, the bounds returned by GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[x].getDefaultConfiguration().getBounds() are overlapping. This causes various secondary bugs...
Comments note that:
The same bug exists on Linux as well, macOS is not affected.
There does not seem to be a simple pure Java workaround.
A fix has been proposed which works for Windows, by not even trying to do the coordinate math in Java, and delegating the solution to native code.
Since it appears that using the JNA (native) implementation appears to work, this seems the best approach for JDK versions 9 to 15. The bug was fixed in JDK16.
According to the bug report, it affects JDK 9+, so it is possible that reverting to JDK 8 may fix the issue, although I saw conflicting accounts on that.
Related
I am currently working on a project in Java where I need to implement a draggable and zoomable graph.
The current implementation involves a graph object having an array of predefined points to draw. And whenever the graph wants to draw a point to the screen, it asks a "graph transformer" to apply affine transformations to the point to get its location on the screen.
The graph transformer contains an X and Y offset as well as a zoom. So basically, a point P will be transformed as follows :
P_x = zoom_x * P_x + offset_x
P_y = zoom_y * P_y + offset_y
The offset is updated when the user drags the graph and the zoom is updated when the user scrolls the mouse wheel.
Everything is working just as intended. The thing is that the zoom is always applied relative to the origin (0,0) of the graph, which is normal. But what I would like to do is apply the zoom relative to the mouse position.
I've been scratching my head for quite some time now, and I am getting a bit confused between the real coordinates and the transformed coordinates.
How could I implement this feature without modifying too much my code ?
Here are my Java methods :
The method transforming points :
public Point transform(Point p) {
Point transformed = new Point();
transformed.x = Math.round(Math.round((m_zoom.x * p.x) + m_offset.x));
transformed.y = Math.round(Math.round((m_zoom.y * p.y) + m_offset.y));
return transformed;
}
The methods called when mouse events are fired :
#Override
public void mousePressed(MouseEvent e) {
m_pressed = e.getPoint();
m_lastCalculatedOffset.x = 0;
m_lastCalculatedOffset.y = 0;
}
#Override
public void mouseDragged(MouseEvent e) {
Point zoomedDragging = new Point();
zoomedDragging.x = Math.round(Math.round(e.getX() - m_pressed.x));
zoomedDragging.y = Math.round(Math.round(e.getY() - m_pressed.y));
m_offset.x += zoomedDragging.x - m_lastCalculatedOffset.x;
m_offset.y += zoomedDragging.y - m_lastCalculatedOffset.y;
m_lastCalculatedOffset.x = zoomedDragging.x;
m_lastCalculatedOffset.y = zoomedDragging.y;
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
m_zoom.x *= Math.pow(ZOOM_FACTOR, e.getWheelRotation());
m_zoom.y *= Math.pow(ZOOM_FACTOR, e.getWheelRotation());
}
Thanks in advance for your help :)
Ok so after taking a break and drawing the problem on paper user graph examples, I managed to find a solution :
If we call ZoomP the location of the mouse when the zoom occurs, once the zoom has been done, the following transformation has to be done to the offset :
newOffset_x = oldOffset_x + (1 - (newZoom_x / oldZoom_x)) * (ZoomP_x - oldOffset_x)
newOffset_y = oldOffset_y + (1 - (newZoom_y / oldZoom_y)) * (ZoomP_y - oldOffset_y)
So here is my updated mouseWheelMoved method for those interested :
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
Point mousePos = e.getPoint();
Point2D.Double newZoom = new Point2D.Double();
newZoom.x = m_zoom.x * Math.pow(ZOOM_FACTOR, e.getWheelRotation());
newZoom.y = m_zoom.y * Math.pow(ZOOM_FACTOR, e.getWheelRotation());
m_offset.x += Math.round(Math.round((1 - (newZoom.x / m_zoom.x)) * (mousePos.x - m_offset.x)));
m_offset.y += Math.round(Math.round((1 - (newZoom.y / m_zoom.y)) * (mousePos.y - m_offset.y)));
m_zoom = newZoom;
}
I don't understand how I can simply clear the screen in Java while using OpenGL. I have searched all over the internet, there is like no real good resource for OpenGL information. Basically I just want to clear the screen and re-draw a circle. Instead my code decides that it isn't going to clear the screen ever, and it most definitely isn't going to draw anything else.. I want it to clear the screen when I press "e", and then draw a new circle. I have two java files.. I will only post relevant code for the sake of any user's who can help me - but will post more code if needed.
In the beginning of my JOGLEventListener.java file I'm also declaring a global var
// Test
GLAutoDrawable test = null;
JOGLEventListener.java
#Override
public void display(GLAutoDrawable gLDrawable)
{
// Set a global variable to hold the gLDrawable
// May not need this?
test = gLDrawable;
GL2 gl = gLDrawable.getGL().getGL2();
gl.glClearColor(backrgb[0], 0, 1, 1);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
backrgb[0]+=0.0005;
if (backrgb[0]> 1) backrgb[0] = 0;
// =============================================
// Draw my circle here
//
// =============================================
// =============================================
System.out.println("Drawing Circle..");
drawCircle(5.0f, 5.0f, 10.0f);
}
// Draw Circle
void drawCircle(float x, float y, float radius)
{
System.out.println("IN DRAWCIRCLE");
int i;
GL2 gl = test.getGL().getGL2();
int lineAmount = 100; //# of triangles used to draw circle
final
//GLfloat radius = 0.8f; //radius
float twicePi = (float) (2.0f * Math.PI);
gl.glBegin(gl.GL_LINE_LOOP);
for(i = 0; i <= lineAmount;i++) {
gl.glVertex2f(
x + (radius * (float)Math.cos(i * twicePi / lineAmount)),
y + (radius* (float)Math.sin(i * twicePi / lineAmount))
);
}
gl.glEnd();
}
#Override
public void keyTyped(KeyEvent e)
{
char key= e.getKeyChar();
System.out.printf("Key typed: %c\n", key);
GL2 gl = test.getGL().getGL2();
if(key == 'e')
{
// WHY ISNT THIS WORKING
// CLEAR THE SCREEN AND DRAW ME A NEW CIRCLE
gl.glClear( gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT );
gl.glLoadIdentity();
//test
float x = 100.0f;
float y = 100.0f;
float twicePi = (float) (2.0f * Math.PI);
float radius = 100f;
System.out.println("Draw Another Circle...");
gl.glBegin(gl.GL_LINE_LOOP);
for(int i = 0; i <= 360;i++)
{
gl.glVertex2f(
x + (radius * (float)Math.cos(i * twicePi / 360)),
y + (radius* (float)Math.sin(i * twicePi / 360))
);
}
gl.glEnd();
}
1) That's deprecated OpenGL, don't use it
2) Don't save the gl object to one global value, always get it from the drawable or the GLContext
3) Use a shader program to render and a vertex buffer to hold the vertices position. But first, I'd suggest you to start a tutorial to learn the basic of OpenGL. Or if you want to get something working asap, clone this hello triangle of mine and start experiment on that
The problem is apparently that you don't swap the front and back buffers.
I'm not familiar with the OpenGL bindings for Java, but I guess that the library already does that for you after it calls the display() function. It doesn't do that after keyTyped().
The way you are supposed to do this is to always draw the scene from scratch inside the display() function based on some internal state. Then in keyTyped() you shall modify that internal state and invalidate the window, which will cause the display() to be called again and redraw the scene properly.
EDIT: Calling display() yourself won't be enough. I can't find how to invalidate the window in Java (in C this would be so much easier). As a dirty hack you can try calling temp.swapBuffers() manually in display, setting setAutoSwapBufferMode(false) and calling display from keyTyped().
I'm looking to make a graphic in Processing that's centered in the middle of the window. I want to be able to change the size of the window and have the graphic remain centred no matter what, so I intend to do this through centering the matrix itself.
How would I go about doing this? Normally I would translate the matrix to the center of the window based on the size of the window itself, but if I'm changing the size then it won't work.
Suggestions?
here, I got this old code that kind of do this...
import processing.opengl.*;
int newCanvasWidth = MIN_WINDOW_WIDTH; // made global to use in draw
int newCanvasHeight = MIN_WINDOW_HEIGHT;
java.awt.Insets insets; //"An Insets object is a representation of the borders of a container"
//from http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/Insets.html
void setup()
{
size(200, 200); // always first line
frame.pack(); //frame.pack() no need for setResizable... plus insets
insets = frame.getInsets();
frame.setResizable(true);
/// for debuging, system depende`nt, at least screen is...
print("MIN_WINDOW_WIDTH = " + MIN_WINDOW_WIDTH);
print(" MIN_WINDOW_HEIGHT = " + MIN_WINDOW_HEIGHT);
print(" screenWidth = " + displayWidth);
println(" screenHeight = " + displayHeight);
}
void draw()
{
background(255);
ellipse(width/2, height/2, width/2, height/2);
}
My code plots 5000 points of time series data in a panel that is 581 pixels wide by default, but this width changes when the user resizes the window. My code also plots several rectangular markers that each identify a local maximum/peak in this same space.
I need to enable the user to right click on any of the rectangular-peak-markers so that the user can manually delete any false peak. The problem is that my code is reporting different x-coordinates than expected when the user right-clicks on a peak-marker. I suspect that the reason may have to do with rounding error in converting from 581 x-pixels back to 5000 data indices. But I am not certain of the reason.
Can anyone suggest a solution that enables my users to manually select one of the above-described peak markers by right-clicking on it?
I am enclosing relevant sections of the code below. My actual code is very, very long, and too complicated to post. But the relevant portions below should be enough for someone to see the logic of my approach, and to then suggest a more effective approach.
The code that declares the class in question is:
class SineDraw extends JPanel implements MouseMotionListener, MouseListener {
// lots of code, including the two segments excerpted below
}
This segment of code overloads the paintComponent of the JPanel so that my data is plotted:
// declare some variables
ArrayList<Double> PeakList = new ArrayList<Double>() // this ArrayList is populated by an extraneous process
visiblePoints = 5000
hstep = getWidth()/visiblePoints //=581/5000 by default, but will change when user resizes window
int numPeaks = PeakList.size();
// scale (y-coordinate) data relative to height of panel
pts = new double[visiblePoints]
for (int i = 0; i < pts.length-1; i++){pts[i]=//data vertical scaled to fill panel;}
// plot the 5000 time-series-data-points within the 581 pixels in x-axis
for (int i = 1; i < visiblePoints; i++) {
int x1 = (int) ((i - 1) * hstep);
int x2 = (int) (i * hstep);
int y1 = (int)pts[i - 1];
int y2 = (int)pts[i];
g2.drawLine(x1, y1, x2, y2);
}
// plot a rectangle for each of the local peaks
for(int m=0;m<=(numPeaks-1);m++){
if(i==(int)(PeakList.get(m)){
int currentVal = (int)pts[(int)(PeakList.get(m)];
g2.drawRect((int)(PeakList.get(m), currentVal, 6, 6);
}
}
This section of code is for handling the right-clicking of the mouse:
public void mousePressed(MouseEvent e){
// check to see if right mouse button was clicked
boolean jones = (e.getModifiers()&InputEvent.BUTTON3_MASK)==InputEvent.BUTTON3_MASK;
if(jones==true){
// test the value returned as x-coordinate when user right-clicks (code always underestimates x-coordinate of local peaks by this test)
double ReverseHstep = visiblePoints/getWidth();
int getX_ConvertedTo_i = (int) (e.getX()*ReverseHstep);
System.out.println("getX_ConvertedTo_i is: "+getX_ConvertedTo_i );
// check to see if peaklist contains a value within the x-coordinates of the user-selected-rectangle
if(PeakList.contains((double)(e.getX()-3))
||PeakList.contains((double)(e.getX()-2))
||PeakList.contains((double)(e.getX()-1))
||PeakList.contains((double)(e.getX()))
||PeakList.contains((double)(e.getX()+1))
||PeakList.contains((double)(e.getX()+2))
||PeakList.contains((double)(e.getX()+3))
){
// handling code will go here, but for now it is a print test that never succeeds because x-coordinate is always underestimated
System.out.println("You just selected a peak!");
}
}
repaint();
}
I suggest you create objects (in this case Rectangles) for each thing you want to be clickable. Here is an over-simplified example of how you can make something you draw clickable. The key thing to take away from this is the mouseClicked method which will display a dialog only if the mouse clicked within the rectangle.
One tricky point is that I wasn't able to figure out how to make the rectangle filled in with color without drawing another rectangle over it. I'll leave that one for you ;-)
public class Canvas extends JPanel implements MouseListener{
private Rectangle rect = new Rectangle(100,100);
public Canvas(){
this.addMouseListener(this);
rect.setSize(100, 100);
}
#Override
public void paintComponent(Graphics g){
g.setClip(rect);
g.setColor(Color.RED);
g.fillRect(0, 0, 100, 100);
}
#Override
public void mouseClicked(MouseEvent e){
if(rect.contains(e.getPoint())){
JOptionPane.showConfirmDialog(this, "Click!");
}
}
// The rest of the MouseListener methods have been cut out
public static void main(String[] a){
JFrame frame = new JFrame("Canvas Thingy");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(0, 0, 300, 300);
frame.add(new Canvas());
frame.setVisible(true);
}
}
I'm using setUndecorated(true); and getRootPane().setWindowDecorationStyle(JRootPane.FRAME); in my jFrame. This works great but now when I maximized my frame it spreads all over the window even taskbar is not visible. What can I do to make frame not to hide taskbar?
Also when I maximize minimize my frame multiple times the cursor is changed to this <-> which is generally used change size of frame when cursor is on the border of frame. Is there anything I can do for this?
A small code then can reproduce the thing:
import javax.swing.JFrame;
import javax.swing.JRootPane;
public class Demo extends JFrame {
public Demo() {
setSize(250,125);
setUndecorated(true);
getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
setVisible(true);
}
public static void main(String[] args) {
new Demo();
}
}
This is a known bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4737788
Quote from this link:
A workaround is to subclass JFrame and
override the setExtendedState method,
catching any maximize events before
they happen and setting the maximum
bounds of the frame appropriately
before calling the superclass's
setExtendedState method.
import java.awt.*;
import javax.swing.*;
public class PFrame extends JFrame
{
private Rectangle maxBounds;
public PFrame()
{
super();
maxBounds = null;
}
//Full implementation has other JFrame constructors
public Rectangle getMaximizedBounds()
{
return(maxBounds);
}
public synchronized void setMaximizedBounds(Rectangle maxBounds)
{
this.maxBounds = maxBounds;
super.setMaximizedBounds(maxBounds);
}
public synchronized void setExtendedState(int state)
{
if (maxBounds == null &&
(state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH)
{
Insets screenInsets = getToolkit().getScreenInsets(getGraphicsConfiguration());
Rectangle screenSize = getGraphicsConfiguration().getBounds();
Rectangle maxBounds = new Rectangle(screenInsets.left + screenSize.x,
screenInsets.top + screenSize.y,
screenSize.x + screenSize.width - screenInsets.right - screenInsets.left,
screenSize.y + screenSize.height - screenInsets.bottom - screenInsets.top);
super.setMaximizedBounds(maxBounds);
}
super.setExtendedState(state);
}
}
Fortega answer worked however, some part is not needed (or no longer needed with Java 8):
The Rectangle does not need to be saved.
The code does not take into account dual screen configuration. In particular, the GraphicsConfiguration will change if the window change screen.
As far as I tested, the only required override is setExtendedState.
When factoring dual screen configuration, at least on Windows, the below code does not work as intended:
Rectangle maxBounds = new Rectangle(screenInsets.left + screenSize.x,
screenInsets.top + screenSize.y,
screenSize.x + screenSize.width - screenInsets.right - screenInsets.left,
screenSize.y + screenSize.height - screenInsets.bottom - screenInsets.top);
On the following dual screen set up:
Left screen 1920x1080 (not primary), position: -1920, 0
Right screen 1920x1080 (primary), position: 0, 0
The maxBounds will contains negative x (-1920) but the setMaximizedBounds is somehow expecting a coordinate in the screen space (where (x,y) starts at (0,0)) , not the virtual screen:
It will set to setMaximizedBounds(x=-1920,y=0,width=1920,height=1050)
Windows will see the window on the left screen (because I have one taskbar per screen showing only window on that screen) however the window won't be shown on the screen because it is off bounds.
If the resolution of the screen, or worse, its scale factor (with a laptop, Windows 10 will apply a scale factor, ex: 25%, making the screen "not so" 1920x1080), then the above code does not adapt. For example, if my configuration have 3 screens with the right most being the primary, the window will badly display on the left and middle screen. I don't think I fixed this in the below code.
The following code work on Windows, with dual screen:
#Override
public synchronized void setExtendedState(final int state) {
if ((state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH) {
final GraphicsConfiguration cfg = getGraphicsConfiguration();
final Insets screenInsets = getToolkit().getScreenInsets(cfg);
final Rectangle screenBounds = cfg.getBounds();
final int x = screenInsets.left + screenBounds.x * 0;
final int y = screenInsets.top + screenBounds.y * 0;
final int w = screenBounds.width - screenInsets.right - screenInsets.left;
final int h = screenBounds.height - screenInsets.bottom - screenInsets.top;
final Rectangle maximizedBounds = new Rectangle(x, y, w, h);
System.out.println("cfg (" + cfg + ") screen.{bounds: " + screenBounds + ", insets: " + screenInsets + ", maxBounds: " + maximizedBounds);
super.setMaximizedBounds(maximizedBounds);
}
super.setExtendedState(state);
}
On a simple JFrame:
Maximizing on the left screen ("screen=0") will print cfg (D3DGraphicsConfig[dev=D3DGraphicsDevice[screen=0],pixfmt=0]) screen.{bounds: java.awt.Rectangle[x=-1920,y=0,width=1920,height=1080], insets: java.awt.Insets[top=0,left=0,bottom=30,right=0], maxBounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1050]
Maximizing on the right screen ("screen=1") will print cfg (D3DGraphicsConfig[dev=D3DGraphicsDevice[screen=1],pixfmt=0]) screen.{bounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1080], insets: java.awt.Insets[top=0,left=0,bottom=30,right=0], maxBounds: java.awt.Rectangle[x=0,y=0,width=1920,height=1050]
Maybe you can set the maximum size of the jFrame and restrict it according to the screen size.
EDIT
Also check out setExtendedState
Starting from Fortega answer, you can make it work even with 125% screen sizi adding
Rectangle screenSize = getGraphicsConfiguration().getBounds();
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
screenSize.setSize(new Dimension(gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()));
......