Move Popup Within Bounds of Client Window

I have a popup view that I'm opening via an onClick event and I'd like to open it in the relative mouse position.

I've achieved this, but now there's a case where the button is close enough to the edge of the client window that it opens partially off the screen.

I want to limit the max X and Y position based on the window size, but this is not so easy because there can be docked view (in this case, a docked north navigation view) that throws things off.

#x and y position for popup, relative to entire screen
xOpen, yOpen = system.gui.convertPointToScreen(event.x, event.y, event)

#open window
window = system.nav.openWindowInstance('Popups/PopUp')

#parent window
parentWindow = system.gui.getParentWindow(event)

#calculate where the end of the poup is
xEnd = xOpen + window.width
yEnd = yOpen + window.height

#how do I find x and y max (width and height of screen)??
#xMax = 1920
#yMax = 1080

#make sure end of popup is not off-screen
if xEnd > xMax:
	xOpen = xOpen - (xEnd - xMax)
	
if yEnd > yMax:
	yOpen = yOpen - (yEnd - yMax)

#set window location
window.setLocation(xOpen,yOpen)

How can I make sure the popup opens within the bounds of the window every time?

Here is a script I developed some time ago for this purpose:

# Developed to be called from a popup window's internalFrameActivated event handler

# Import the necessary Javax Swing classes
from javax.swing import JFrame,SwingUtilities

# Get an unambiguous reference to the 'popupWindow' component from the event source
popupWindow = event.source

# Find the nearest ancestor JFrame of the 'popupWindow' component which will be the parent window that the popup was opened from
parentJFrame = SwingUtilities.getAncestorOfClass(JFrame, popupWindow)

# Set a margin value for positioning the 'popupWindow'
parentJFrameMargin = 100

# Try to get the mouse position relative to the 'parent' JFrame
try:
	popupWindowX = parentJFrame.getMousePosition(True).x
	popupWindowY = parentJFrame.getMousePosition(True).y
	
# If an exception occurs (e.g., mouse position not available), set default values of (0,0) [Upper left corner] plus the specified margin
except:
	popupWindowX = parentJFrameMargin
	popupWindowY = parentJFrameMargin
	
# Ensure that the 'popupWindow' is within the boundaries of the 'parent' JFrame
if popupWindowX < parentJFrameMargin:
	popupWindowX = parentJFrameMargin
elif popupWindowX + popupWindow.width > parentJFrame.width - parentJFrameMargin:
	popupWindowX = parentJFrame.width - popupWindow.width - parentJFrameMargin
if  popupWindowY < parentJFrameMargin:
	popupWindowY = parentJFrameMargin
elif popupWindowY + popupWindow.height > parentJFrame.height - parentJFrameMargin:
	popupWindowY = parentJFrame.height - popupWindow.height - parentJFrameMargin 
	
# Set the new location of the 'popupWindow' based on the calculated positions
popupWindow.setLocation(popupWindowX, popupWindowY)

It finds the Jframe of the window the popup was launched from, and calculates if the window can be positioned at the mouse cursor without overlapping the side of the window. If it can't be done, it positions it as close as possible. It also has an adjustable margin parameter which is currently set to 100 pixels, so the window will actually be contained within a 100 pixels of the edge of the parent window.

2 Likes

Thanks for the script! I'll try to adapt this for my use.

I have one call that opens the popup and another that repositions the popup, all within the onActionPerformed for a button, like so:

if event.source.parent.parent.clickable:
	window = Nav.popup1(event)
	Nav.moveWindowRelative(event, window)
def popup1(event):
	'''
		Calls pop with path defined on the root container
		
		Args:
			event (obj)				:	event handler (typically mouse click or button action)
			
		Returns:
			window (obj)			:	window that was opened
	'''
	
	#get component path from root container
	param1 = event.source.parent.parent.path
	
	#call popup with path
	window = system.nav.openWindowInstance('Popups/PopUp', {'path' : param1})
	system.nav.centerWindow(window)
	
	return window

If I use your code as-is

def moveWindowRelative(event, window):
	from javax.swing import JFrame,SwingUtilities
	# Get an unambiguous reference to the 'popupWindow' component from the event source
	popupWindow = event.source
	
	# Find the nearest ancestor JFrame of the 'popupWindow' component which will be the parent window that the popup was opened from
	parentJFrame = SwingUtilities.getAncestorOfClass(JFrame, popupWindow)
	
	# Set a margin value for positioning the 'popupWindow'
	parentJFrameMargin = 100
	
	# Try to get the mouse position relative to the 'parent' JFrame
	try:
		popupWindowX = parentJFrame.getMousePosition(True).x
		popupWindowY = parentJFrame.getMousePosition(True).y
	
	# If an exception occurs (e.g., mouse position not available), set default values of (0,0) [Upper left corner] plus the specified margin
	except:
		popupWindowX = parentJFrameMargin
		popupWindowY = parentJFrameMargin
	
	# Ensure that the 'popupWindow' is within the boundaries of the 'parent' JFrame
	if popupWindowX < parentJFrameMargin:
		popupWindowX = parentJFrameMargin
	elif popupWindowX + popupWindow.width > parentJFrame.width - parentJFrameMargin:
		popupWindowX = parentJFrame.width - popupWindow.width - parentJFrameMargin
	if  popupWindowY < parentJFrameMargin:
		popupWindowY = parentJFrameMargin
	elif popupWindowY + popupWindow.height > parentJFrame.height - parentJFrameMargin:
		popupWindowY = parentJFrame.height - popupWindow.height - parentJFrameMargin 
	
	# Set the new location of the 'popupWindow' based on the calculated positions
	popupWindow.setLocation(popupWindowX, popupWindowY)

The window always just opens at 0,0.

If I replace the las popupWindow.setLocation with my window.setLocation it still opens off the screen.

I'll try and do some troubleshooting with the console.

*edit: so, when I print the JFrame that is found it looks like this:

com.inductiveautomation.ignition.designer.IgnitionDesigner[frame1,1912,-8,1936x1056,invalid,layout=java.awt.BorderLayout,title=Grain_SCC - Ignition-LPTP134 - Ignition Designer,resizable,maximized,defaultCloseOperation=DO_NOTHING_ON_CLOSE,rootPane=javax.swing.JRootPane[,8,31,1920x1017,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=javax.swing.plaf.synth.SynthBorder@7071c1,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]

If you're calling my script from the button that launches the popup, it won't work "as is".

My script was developed to be called from the pop-up window itself. Specifically, the internalFrameActivated event handler.

Right, that's what I'm going to try and change. I just don't know enough about JFrames

Delete the popupWindow = event.source line, and rename your passed in window variable popupWindow, and the script will probably work.

1 Like

There we go! I replaced it in the setLocation call, but nowhere else. That's perfect.

# Import the necessary Javax Swing classes
from javax.swing import JFrame,SwingUtilities

def moveWindowRelative(window):
	'''
		Moves a popup window to a position
	'''
	
	
	# Find the nearest ancestor JFrame of the window component which will be the parent window that the popup was opened from
	parentJFrame = SwingUtilities.getAncestorOfClass(JFrame, window)
	
	# Set a margin value for positioning the window
	parentJFrameMargin = 30
	#initially assume a window move is required
	moveRequired = True
	
	# Try to get the mouse position relative to the parent JFrame
	try:
	    windowX = parentJFrame.getMousePosition(True).x
	    windowY = parentJFrame.getMousePosition(True).y

	# If an exception occurs (e.g., mouse position not available), do not move the window
	except:
		moveRequired = False
		
	if moveRequired:
	
		# Ensure that the window is within the boundaries of the parent JFrame
		if windowX < parentJFrameMargin:
		    windowX = parentJFrameMargin
		elif windowX + window.width > parentJFrame.width - parentJFrameMargin:
		    windowX = parentJFrame.width - window.width - parentJFrameMargin
		if  windowY < parentJFrameMargin:
		    windowY = parentJFrameMargin
		elif windowY + window.height > parentJFrame.height - parentJFrameMargin:
		    windowY = parentJFrame.height - window.height - parentJFrameMargin 
		
		# Set the new location of the window based on the calculated positions
		window.setLocation(windowX, windowY)
1 Like

I needed just this for my popups, and determined that the best event handler on the popup is the visionWindowOpened event. Your script works perfectly otherwise, and helped me a ton!

When I used internalFrameActivated, if the user clicks off of the popup and back to it, the script triggers again causing the popup to jump to a new location based on where the user clicks on the popup.

You also can't use internalFrameOpened unless your cache policy on the popup is set to never, as any cached windows/popups will not run the script a second time and the popup will show in the last location.

I finally moved it to visionWindowOpened, and it works consistenly with or without caching and without jumping when clicking off and back on a popup.

That makes sense. In my application, I close the pop-up with internal frame deactivated, so its counterpart internal frame activated seemed appropriate for my initialization script. If I were leaving the popup open, I would definitely have to take your advice and change the way that part of the script is called.

I get error on with this line. I think it needs an answer.

If an exception occurs (e.g., mouse position not available), do not move the window

	moveRequired = False

What error are you getting? Most likely you didn't copy/paste the script exactly as written or modified it in some way that's breaking it. Could even be your indentation is off/wrong.

The indentation of the comment could be causing the issue. Make it match the line it's above.

# If an exception occurs (e.g., mouse position not available), do not move the window
Move Required = False

This is what I get for an error.

You have an incomplete try: block above that point, that expects consistent indentation.

do you have an idea what script I should put to that try: block you mentioned?

The try must be accompanied by an except clause like this:

except:
		#if an exception occurs (e.g., mouse position not available), do not move the window 		 
		moveRequired = False

Somehow, it must have got omitted from his code example. For reference, it is included in his prior example a few posts up.

1 Like

Hi Amark did you get this work? I cant get it to work on my end. It seems like it is missing something after the moveRequired = False. I tried to add the except script on it and still did not work. Not sure what im doing wrong.

Ok this works. but the script is still not positioning the popup inside the window.

How are you calling it? Where are you storing it? Alex's version is written for the project library.

image
If the function were placed in a library script unimaginatively named libararyScripts, and the popup were being opened by a button, then the script would need to be called from the action performed event handler of the button in this way:

# From the action performed event handler of the button that opens the popup

# Define needed parameters for the opening window
path = 'path/to/popup'
parameter = 'someParameter' # Optional
value = 3 # Optional

# Open the window and assign it to a passable variable
# ...(The system function returns the window instance)
window = system.nav.openWindowInstance(path , {parameter: value})

# Call the library script and pass in the event and the window,
# ...so the script can properly postion window before it is painted
libararyScripts.moveWindowRelative(event, window)

Alternatively, the script I originally posted could simply be placed in the popup's visionWindowOpened event handler, and it would just work with no need to do anything from the button or action that deploys the popup, but in my opinion, for generic stuff like this that will likely need to be reused throughout a project, it's a better practice to go the library script route because when the need arises to change the script in some way, it will be easier to find, and it will only need to be touched in one place.

1 Like

I don't know how the except got omitted, considering I copy-pasted the code, but I've edited the code above to include the except.

This is designed as a library function and is called like so

1 Like