[IGN-8005]Popup Calendar component size for touch panel unusable

I'm using the Popup Calendar component to selected dates which is great for PC clients, but it's completely unusable for clients on touch panels as the buttons are simply too small.

Is there anything I can do to make this bigger? Or something else that I can use?

I've got this same issue, did you ever find a solution?

Most of the component's buttons get progressively larger the wider the component is.

While you can make most of it bigger by making the drop down wider, it seems the buttons at the bottom don't scale.

I would imagine there's a way if you dug deeper into the component, for example, these two posts show how to change bits of components that normally you can't change with what appears to be Java?:

I'm not sure where to start with something like that though, is there documentation somewhere that shows what each component within the component is? Or are people just figuring this stuff out by trial and error with repeated .getComponent()'s, like what is shown in the second example?

It's not exactly Java; it's Jython.

While IA's documentation is thorough, navigating and manipulating component hierarchy is more of a skill than a supported behavior. I was imagining using transform to widen the component when the popup calendar is shown, but if that won't change the buttons, then you'll have to dig them out of the component. Without experimenting, I'm not sure how well the layout will tolerate the size changes. It doesn't look like there is a lot of room in the popup. When I get time, if nobody comes up with anything, I'll look at it.

1 Like

Here's the normal calendar to demonstrate behaviour, you can see that the buttons don't scale; the section in red never scales but the purple does.

I see you're the user that figured out both of the examples in the links I provided lol. What was your process in modifying the components in those ways? Did you look at the API Documentation and see if the component was contained within?

For example, I can see on the overview page, there's the tab strip, and within are the components you imported at the beginning of the script (ArrowHoldDown, LeftRightArrowPanel) in the first link:

from com.inductiveautomation.factorypmi.application.components.util import HoldDownArrowButton
from com.inductiveautomation.factorypmi.application.components.tabstrip import LeftRightArrowPanel

If these don't exist in the documentation, then do you need to resort to trial and error to see which components exist, which is why you were running these tests for the calendar edits instead of importing components like the tab strip? You essentially need to find what the name of your components are, so that you can "dig them out"?

calender = system.gui.getParentWindow(event).getComponentForPath('Root Container.Date_Group.EndDate_dropdown')
try:
	print type(calender)
	print type(calender.getComponent(0))
	print type(calender.getComponent(0).getComponent(1))
	print type(calender.getComponent(0).getComponent(1).getComponent(0))
	print type(calender.getComponent(0).getComponent(1).getComponent(0).getComponent(1))
	print type(calender.getComponent(0).getComponent(1).getComponent(0).getComponent(1).getComponent(1))
except:
	pass

Which returns:

<type 'com.inductiveautomation.factorypmi.application.components.PMIDateTimeSelector'>
<type 'com.inductiveautomation.ignition.client.util.gui.date_selector.Titled_date_selector'>
<type 'com.inductiveautomation.ignition.client.util.gui.date_selector.Navigable_date_selector'>
<type 'com.inductiveautomation.ignition.client.util.gui.date_selector.Time_date_selector'>
<type 'com.inductiveautomation.ignition.client.util.gui.date_selector.Date_selector_panel'>
<type 'javax.swing.JPanel'>

So in theory, I should be able to do (and what you would do if you were to tackle this yourself?) is a bunch of .getComponents(x) with varying nesting, print out the type, find one that sounds like it's referring to the calendar buttons, and then try to set the size like you did for the tab strip buttons?:

def tabStripArrowEditor(tabStrip):
	if tabStrip.componentCount > 0:
		for component in tabStrip.getComponents():
			if isinstance(component, LeftRightArrowPanel):
				size = Dimension(arrowWidth*2,arrowHeight)
				component.setPreferredSize(size)
				component.setSize(size)
				tabStripArrowEditor(component)
				return
			elif isinstance(component, HoldDownArrowButton):
				size = Dimension(arrowWidth,arrowHeight)
				component.setPreferredSize(size)
				component.setSize(size)
			else:
				tabStripArrowEditor(component)

Does that sound about right, or is there a smarter way to go about doing this?

In this case, I wrote a script to iterate through the components and print their type. I noticed that there were precisely five JButtons, so printed their button text in the console to confirm they were the five components I was after. Then, I was able to achieve the desired result with this script:

if event.propertyName == "componentRunning":
	from java.awt import Dimension
	popupCalender = event.source
	newWidth = int(float(popupCalender.width) * .2) #there are five buttons, and one fifth is .2
	newHeight = newWidth
	def setButtonSizes(popup):
		for component in popup.getComponents():
			if "JButton" in str(component.__class__):
				if component.text == "OK":
					component.setPreferredSize(Dimension(newWidth, newHeight))
				else:
					component.parent.setPreferredSize(Dimension(4 * newWidth, newHeight))
					component.setPreferredSize(Dimension(newWidth, newHeight))
			else:
				setButtonSizes(component)
	popupField = popupCalender.getClass().getDeclaredField('popup')
	popupField.setAccessible(True)
	popup = popupField.get(popupCalender)
	setButtonSizes(popup)

The script determines how wide the buttons need to be to fill the entire popup width by multiplying the width of the popup calendar by 1/5 [0.2]. It then iterates through the subcomponents and locates all of the JButtons and resizes them. Four of the buttons are nested in a JPanel, so I found that I needed to resize the panel to accommodate the larger buttons.

Here is the result:
image

Note: This is a property change event script that only fires when the window first opens. To test this in the designer, close the window and reopen it while in preview mode.

1 Like

So I found the buttons using a recursive script, and when I found 'JButton' I set the size to 100x100 arbitrarily, similar to what you are doing (but far more crude lol). I'm also using the calendar component for now to experiment with a few less variables.

Script:

size = dimension(width=100,height=100)
calendar = event.source.parent.getComponent('Calendar')
def RecursiveSearch(component):
	for item in component.getComponents():
		if 'JButton' in str(item):
			item.setPreferredSize(size)
		print type(item), str(item)
		RecursiveSearch(item)
RecursiveSearch(calendar)

Result:

<type 'javax.swing.JPanel'> javax.swing.JPanel[,34,0,123x32,layout=javax.swing.BoxLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777217,maximumSize=,minimumSize=,preferredSize=]
<type 'javax.swing.JButton'> javax.swing.JButton[,0,0,32x32,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@53c3d529,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=100,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=<<,defaultCapable=true]
<type 'javax.swing.Box$Filler'> javax.swing.Box$Filler[,32,0,3x32,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=3,height=32767],minimumSize=java.awt.Dimension[width=3,height=0],preferredSize=java.awt.Dimension[width=3,height=0]]
<type 'javax.swing.JButton'> javax.swing.JButton[,35,0,25x32,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@4ae4edd7,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=100,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=<,defaultCapable=true]
<type 'javax.swing.Box$Filler'> javax.swing.Box$Filler[,60,0,3x32,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=3,height=32767],minimumSize=java.awt.Dimension[width=3,height=0],preferredSize=java.awt.Dimension[width=3,height=0]]
<type 'javax.swing.JButton'> javax.swing.JButton[,63,0,25x32,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@508f3438,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=100,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=>,defaultCapable=true]
<type 'javax.swing.Box$Filler'> javax.swing.Box$Filler[,88,0,3x32,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=3,height=32767],minimumSize=java.awt.Dimension[width=3,height=0],preferredSize=java.awt.Dimension[width=3,height=0]]
<type 'javax.swing.JButton'> javax.swing.JButton[,91,0,32x32,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@4324c68f,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=100,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=>>,defaultCapable=true]
<type 'javax.swing.Box$Filler'> javax.swing.Box$Filler[,157,0,3x32,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=3,height=32767],minimumSize=java.awt.Dimension[width=3,height=0],preferredSize=java.awt.Dimension[width=3,height=0]]
<type 'javax.swing.JButton'> javax.swing.JButton[,160,0,36x32,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@51c3ec62,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=100,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=OK,defaultCapable=true]
<type 'javax.swing.Box$Filler'> javax.swing.Box$Filler[,196,16,34x0,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=32767,height=0],minimumSize=java.awt.Dimension[width=0,height=0],preferredSize=java.awt.Dimension[width=0,height=0]]

However, my buttons didn't change size at all, though you can see in the preferred width output that they did update to 100x100.

What allowed the buttons to resize when you did it, while mine did not? You mention the JPanel, which I didn't touch. Are the buttons somehow confined to the JPanel, and if they cannot fully change size, they don't at all? Or is it my testing method? I'm running my script off a pushbutton for testing purposes so I don't have to worry about more complicated trigger methods.

Yes.

1 Like

I can see now that if I comment out the panel size adjustment, I end up with no adjustment at all, thanks.

I think the width is probably sufficient to be able to use the component on a touchscreen, but I'd like to also like to change the height of the buttons and the time spinner, if only for learning purposes.

Using your code as a base and playing around with the height, it seems only the panel increases in height (red arrow), not the buttons themselves:

Is there a second panel that is limiting the height of the buttons? Maybe it's the box filler? How do I find the limiting factor here? Based on how you used panel dimensions of 4 x newWidth (vs 5 x newWidth), it would seem there's one JPanel for the date buttons (green), and then the OK button and the date button JPanel is contained within a parent JPanel (purple)? The output below from a recursive search shows two JPanels, one will be the one we edited, the other could be a parent?

javax.swing.JPanel[,0,658,616x100,invalid,layout=javax.swing.BoxLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777217,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=1600,height=100]]
javax.swing.Box$Filler[,0,50,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=32767,height=0],minimumSize=java.awt.Dimension[width=0,height=0],preferredSize=java.awt.Dimension[width=0,height=0]]
javax.swing.JPanel[,0,0,508x100,invalid,layout=javax.swing.BoxLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777217,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=622,height=200]]
javax.swing.JButton[,0,34,32x32,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@392a6d9d,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=124,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=<<,defaultCapable=true]
javax.swing.Box$Filler[,32,0,3x100,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=3,height=32767],minimumSize=java.awt.Dimension[width=3,height=0],preferredSize=java.awt.Dimension[width=3,height=0]]
javax.swing.JButton[,35,34,25x32,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@2f584bc7,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=124,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=<,defaultCapable=true]
javax.swing.Box$Filler[,60,0,3x100,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=3,height=32767],minimumSize=java.awt.Dimension[width=3,height=0],preferredSize=java.awt.Dimension[width=3,height=0]]
javax.swing.JButton[,63,34,25x32,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@30b1943a,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=124,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=>,defaultCapable=true]
javax.swing.Box$Filler[,88,0,3x100,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=3,height=32767],minimumSize=java.awt.Dimension[width=3,height=0],preferredSize=java.awt.Dimension[width=3,height=0]]
javax.swing.JButton[,91,34,32x32,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@242100ef,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=124,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=>>,defaultCapable=true]
javax.swing.Box$Filler[,508,0,3x100,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=3,height=32767],minimumSize=java.awt.Dimension[width=3,height=0],preferredSize=java.awt.Dimension[width=3,height=0]]
javax.swing.JButton[,511,34,104x32,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder@434e257d,flags=16777504,maximumSize=,minimumSize=,preferredSize=java.awt.Dimension[width=124,height=100],defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=java.awt.Insets[top=1,left=3,bottom=1,right=3],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=OK,defaultCapable=true]
javax.swing.Box$Filler[,615,50,0x0,invalid,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=java.awt.Dimension[width=32767,height=0],minimumSize=java.awt.Dimension[width=0,height=0],preferredSize=java.awt.Dimension[width=0,height=0]]

I took another look at this, and I found that the buttons' maximum size parameter that was limiting the height. Here is an updated script that produces the desired result:

if event.propertyName == "componentRunning":
	from java.awt import Dimension
	popupCalender = event.source
	def getJButtons(popup):
		jbuttons = []
		for component in popup.getComponents():
			if "JButton" in str(component.__class__):
				jbuttons.append(component)
			else:
				jbuttons.extend(getJButtons(component))
		return jbuttons
	popupField = popupCalender.getClass().getDeclaredField('popup')
	popupField.setAccessible(True)
	popup = popupField.get(popupCalender)
	jbuttons = getJButtons(popup)
	buttonPanel = next((button.parent for button in jbuttons if button.text != "OK"), None)
	oneFifth = .2 #factor for making 5 equal width buttons 
	newWidth = int(float(popupCalender.width) * oneFifth) 
	newHeight = newWidth
	buttonPanel.setPreferredSize(Dimension(4 * newWidth, newHeight))
	for button in jbuttons:
		button.setMaximumSize(Dimension(newWidth, newHeight))
		button.setPreferredSize(Dimension(newWidth, newHeight))

Here is the result:

This script adjusts the max size parameter to match the preferred size. I also refactored the original part of the script to make it where the preferred size of the jpanel only gets set once instead of four times.

2 Likes

A few years too late...

Are there any other components what other components need similar treatment (in some capacity) for touchscreen mode? The alarm status tables, probably? The * management components?

What I'm considering:
Rename the 'scrollbar size' property in the Vision project properties as a 'minimum touch target' size, and making sure that any first-party components that manage their own UI adhere to that value (if they can). I'd need to know 1. if that would suffice for folks and 2. what components are the biggest pain points, to prioritize.

3 Likes

This is awesome, thank you so much! I watched a few Swing tutorials on box layout and struggled for a few hours trial and error trying to adjust that panel, and ultimately had no luck lol.

1 Like

The Calendar and Tab Strip arrows have so far been the biggest issues we've encountered. Luckily Justin had solutions for both.

Scroll bar width settings per component would be nice, I believe you can only change via the project properties? The spinners also appear to inherit from the scroll bar size? I'm sure a few other components do as well?
In my situation now, we've basically finished our project (which is basically our first) without changing the project properties scroll bar width. It's at the point where we're almost done, but realized too late where that setting is. I'm a little scared to change it now due to the effects it could have across all the windows where everything has been nicely sized over the last few months haha. It would be nice to have them use a default (project setting), but then be able to set an override in the component properties to tweak components as needed without affecting all of them.

Some stuff I've just become used to at this point, I'll try to look at things anew and ask the customer what else they've noticed.

Edit: It would also be really nice to set the size of the popup calendar's popup, independent from just however wide the component is. We had to rework some screen organization in order to use a much wider than necessary component (below), so that the calendar pops up at a decent size.
image

I'm not sure I would be satisfied with this approach since a super wide drop down looks weird. Another option would be to move the script to a popupMenuListener, then you could add the additional functionality of resizing the popup independent of the component width when it is about to become visible:

Here is the propertyChange event handler script that produces the result shown in the video:

Source Code
if event.propertyName == "componentRunning":
	from javax.swing.event import PopupMenuListener
	from java.awt import Dimension
	class PopupVisibilityListener(PopupMenuListener):
		def popupMenuWillBecomeVisible(self, event):
			from java.awt import Dimension
			popup = event.source
			def getJButtons(popup):
				jbuttons = []
				for component in popup.getComponents():
					if "JButton" in str(component.__class__):
						jbuttons.append(component)
					else:
						jbuttons.extend(getJButtons(component))
				return jbuttons
			def setJButtonSizes():
				jbuttons = getJButtons(popup)
				buttonPanel = next((button.parent for button in jbuttons if button.text != "OK"), None)
				oneFifth = .2 #factor for making 5 equal width buttons 
				newWidth = int(float(popup.preferredSize.width) * oneFifth) 
				newHeight = newWidth
				buttonPanel.setPreferredSize(Dimension(4 * newWidth, newHeight))
				for button in jbuttons:
					button.setMaximumSize(Dimension(newWidth, newHeight))
					button.setPreferredSize(Dimension(newWidth, newHeight))
			scaleFactor = 2
			newPopupWidth = scaleFactor * popup.preferredSize.width
			newPopupHeight = scaleFactor * popup.preferredSize.height
			popup.setPreferredSize(Dimension(newPopupWidth, newPopupHeight))
			system.util.invokeLater(setJButtonSizes)
		def popupMenuWillBecomeInvisible(self, event):
			pass
		def popupMenuCanceled(self, event):
			pass
	popupCalender = event.source.parent.getComponent('Popup Calendar')
	popupField = popupCalender.getClass().getDeclaredField('popup')
	popupField.setAccessible(True)
	popup = popupField.get(popupCalender)
	popup.addPopupMenuListener(PopupVisibilityListener())

Note: When adding a listener, it is important to do it in a place that will only run once. In this case, I am using the componentRunning event, so the listener only gets added when the window is initially opened.

1 Like