Power Table - Dropdown Configuration

Hey folks,

I've got a power table in place with a working configureEditor script (see below). However, I am running into an issue where the contents of the dropdown menu is wider than the column itself. Is there a solution here? Ideas that come to mind:

  • Resize column when dropdown opened to match max width of dropdown
    • Is there a way to determine max width of dropdown?
  • Resize dropdown independent of column
  • Bypass the dropdown entirely and use a popup (not ideal).

Anyone have thoughts on how to best accomplish this? Thanks in advance.

configureEditor Script
def configureEditor(self, colIndex, colName):
	#create options list
	dropdownList = []
	for row in system.dataset.toPyDataSet(self.recipeSteps):
		#remove line breaks
		description = row[1].replace('\r', '')
		description = description.replace('\n', '')
		#create tuple
		dropdownTuple = (int(row[0]), description)
		#append to list
		dropdownList.append(dropdownTuple)
	
	colList = ['Step 1', 'Step 2', 'Step 3', 'Step 4', 'Step 5', 'Step 6', 'Step 7', 'Step 8', 'Step 9', 'Step 10']
	# Example to use a dropdown list for the 'Shift' column:
	if colName in colList:
		return {'options': dropdownList}

I would do #1. Set the "preferred width" in the column model, IIRC.

1 Like

Thanks for the suggestion, Phil.

However, I'm having a hard time finding mention of that property / configuration. Can you point me in the right direction?

For now, I've added an onClick script to read in the default column config, edit the width value, and write it back, but it feels a little hacky.

General concepts here:

Some documentation to follow:

VisionAdvancedTable, JideSoft Grids (JideTable), JTable, TableModel, TableColumnModel

3 Likes

I had the morning off, and this seemed like a fun thing to try, so I developed this onMouseClick Extension Function script that reliably sets the column width to the largest string in the dropdown selection:

#def onMouseClick(self, [...], event):
	from javax.swing import JComboBox
	from java.awt.font import FontRenderContext, TextLayout
	from java.awt import Font
	if hasattr(self.table.getCellEditor(rowIndex, colIndex), 'component'):
		editorComponent = self.table.getCellEditor(rowIndex, colIndex).component
		if isinstance(editorComponent, JComboBox):
			tableFont = self.font
			widthSettingFont = Font(Font.MONOSPACED, tableFont.style, tableFont.size)
			selectedColumnIndex = colIndex
			columnModel = self.table.columnModel
			selectedColumn = columnModel.getColumn(selectedColumnIndex)
			selectedColumnWidth = selectedColumn.getPreferredWidth()
			dropdown = editorComponent.getUI().getAccessibleChild(editorComponent, 0)
			items = [editorComponent.renderer.getListCellRendererComponent(dropdown.getList(), editorComponent.model.getElementAt(row), -1, False, False).getText() for row in range(editorComponent.model.size)]
			maxItemWidth = max([int(TextLayout(item, widthSettingFont, FontRenderContext(None, False, False)).bounds.width) for item in items])
			if selectedColumnWidth < maxItemWidth:
				widthDifference = float(maxItemWidth - selectedColumnWidth)
				otherColumnCount = float(columnModel.columnCount - 1)
				if otherColumnCount > 0:
					widthOffset = int(widthDifference/otherColumnCount)
					for otherColumnIndex in range(columnModel.columnCount):
						if otherColumnIndex != selectedColumnIndex:
							otherColumn = columnModel.getColumn(otherColumnIndex)
							otherColumnWidth = otherColumn.getWidth()
							otherColumn.setPreferredWidth(otherColumnWidth - widthOffset)
						else:
							selectedColumn.setPreferredWidth(maxItemWidth)

Here is the result:
image

Originally, I wanted to just set the preferred width of the clicked column, but I found that no matter what the column resize policy was, it would not reliably achieve the full width, so I had to add a calculated offset that causes each of the other columns to loose their share of the amount the selected column is gaining.

While moving the columns around works, I would prefer to change the dropdown width, but unfortunately, I've run out of time before I could achieve the proper result.

First Dropdown Width Adjustment Attempt
#def onMouseClick(self,[...], event):
	from javax.swing import JComboBox
	from java.awt import Dimension
	from java.awt.font import FontRenderContext, TextLayout
	from java.awt import Font
	if hasattr(self.table.getCellEditor(rowIndex, colIndex), 'component'):
		editorComponent = self.table.getCellEditor(rowIndex, colIndex).component
		if isinstance(editorComponent, JComboBox):
			tableFont = self.font
			widthSettingFont = Font(Font.MONOSPACED, tableFont.style, tableFont.size)
			dropdown = editorComponent.getUI().getAccessibleChild(editorComponent, 0)
			items = [editorComponent.renderer.getListCellRendererComponent(dropdown.getList(), editorComponent.model.getElementAt(row), -1, False, False).getText() for row in range(editorComponent.model.size)]
			maxItemWidth = max([int(TextLayout(item, widthSettingFont, FontRenderContext(None, False, False)).bounds.width) for item in items])
			dropdown.setPreferredSize(Dimension(maxItemWidth, dropdown.getPreferredSize().height))

The above script produces the following result:
image

As can be seen in the picture, the dropdown has achieved the proper width, but the scrollable viewport has remained stubbornly the same size. Even when I get them and change them directly in the following manner:

dropdown.getComponent(0).setPreferredSize(Dimension(maxItemWidth, dropdown.getPreferredSize().height))
dropdown.getComponent(0).viewport.setPreferredSize(Dimension(maxItemWidth, dropdown.getPreferredSize().height))

I've also tried changing the size of the list itself:

dropdown.getList().setPreferredSize(Dimension(maxItemWidth, dropdown.getPreferredSize().height))

...and I've played around with the renderer a bit, but it just never came together. Perhaps I'll look at this again later with a fresh set of eyes.

Edit: See next post for dropdown width adjustment.

2 Likes

Update: I figured out how to dynamically set the dropdown menu widths to the width of the longest text item in the list.

Here is the result:
image

The main problem was that the scroll pane had a maximum width setting that was equal to the cell size. After fixing this, code had to be added to ensure that the JComboBox was showing and the dropdown menu was open prior to the changes being applied.

Here is the working script:

Second Attempt Script
#def onMouseClick(self, [...], event):
	from javax.swing import JComboBox, JScrollPane
	from java.awt import Dimension
	from java.awt.font import FontRenderContext, TextLayout
	from java.awt import Font
	def setSizes(dropdown):
		dropdown.setMaximumSize(Dimension(2*maxItemWidth, 2*dropdown.getPreferredSize().height))
		dropdown.setMinimumSize(Dimension(0, 0))
		dropdown.setPreferredSize(Dimension(maxItemWidth, dropdown.getPreferredSize().height))
		for component in dropdown.getComponents():
			if isinstance(component, JScrollPane):
				setSizes(component)
				break
	if hasattr(self.table.getCellEditor(rowIndex, colIndex), 'component'):
		editorComponent = self.table.getCellEditor(rowIndex, colIndex).component
		if isinstance(editorComponent, JComboBox):
			tableFont = self.font
			widthSettingFont = Font(Font.MONOSPACED, tableFont.style, tableFont.size)
			dropdown = editorComponent.getUI().getAccessibleChild(editorComponent, 0)
			items = [
				editorComponent.renderer.getListCellRendererComponent(dropdown.getList(), editorComponent.model.getElementAt(row), -1, False, False).getText()
				for row in xrange(editorComponent.model.size)
			]
			maxItemWidth = int(TextLayout(max(items, key=len), widthSettingFont, FontRenderContext(None, False, False)).bounds.width)
			if not editorComponent.popupVisible and editorComponent.showing:
				editorComponent.setPopupVisible(True)
			setSizes(dropdown)

Edit: This script has a bug where if you click on the same cell multiple times the dropdown regresses due to the way the cell retains focus and prevents the mouseClick extension function from firing every time. See post 25 for the updated solution.

5 Likes

Wow! To say "thank you!" would be entirely insufficient. This was beyond my means, I really appreciate you taking the time to tackle my trivial request. I'll take a shot at implementing it and report back.

2 Likes

@justinedwards.jle may have made it look and feel trivial, I assure you it is not. So don’t be hard on yourself.

3 Likes

So, its definitely working, with a couple hiccups.
Here's what I'm encountering, which could be related to the size of my drop down options (~250 options).

When I click a cell, the dropdown is full width but the text is still only cell width. After a noticeable and inconsistent delay, the text expands to fit the dropdown. If it doesn't resolve, usually scrolling will cause it to.

Any thoughts on how to improve performance? These drop downs all have the same options, so I can pull the longest string on startup to simplify that piece of it. Any other thoughts?

By this, you mean the first time you click the column, you see this delay, but on subsequent clicks, the dropdown works properly? Interesting. It seems more like an order of operations issue than performance.

1 Like

That was my poor communication. it appears to happen every time.

Not sure if it will help a lot, but changing this line to be a generator instead of a full comprehension should improve performance:

maxItemWidth = max(int(TextLayout(item,widthSettingFont,FontRenderContext(None,False,Flase)).bounds.width) for item in items)

Edit:
Also, might break from the loop once you've found the JScrollPane:

for component in dropdown.getComponents():
    if isinstance(component, JScrollPane):
        setSizes(component)
        break
2 Likes

Picking the longest string and then only checking its width would also be a big optimization. Font rendering calculations are slow and CPU-bound in Java Swing.

5 Likes
			items = [
				editorComponent.renderer.getListCellRendererComponent(dropdown.getList(), editorComponent.model.getElementAt(row), -1, False, False).getText()
				for row in xrange(editorComponent.model.size)
			]
			maxItemWidth = int(TextLayout(max(len(item) for item in items), widthSettingFont, FontRenderContext(None, False, False)).bounds.width)
1 Like

Just as an FYI, I hard-coded maxItemWidth just to try to confirm it was/wasn't the source the problem and it doesn't fix it.

Alright, consider me nerd sniped... I'll probably dig into this in the debugger if @justinedwards.jle doesn't come up with a fix before me :slight_smile:

4 Likes

It could be a day or so before I can look at this again. My inclination would be to move the script to a custom method with relevant parameters, so I could experiment with calling it from other places.

1 Like

Thank you all for your help on this one! This is still miles ahead of what I was doing and so appreciated.

My caveman solution of resizing the selected column - for posterity
def onMouseClick(self, rowIndex, colIndex, colName, value, event):
	colList = ['Step 1', 'Step 2', 'Step 3', 'Step 4', 'Step 5', 'Step 6', 'Step 7', 'Step 8', 'Step 9', 'Step 10']
	if colName in colList:
		import re
		#read default sizing and write column width from there
		sizing = self.columnSizing
		
		#convert from tab delimited to list
		columnList = re.split(r'\t',sizing)
		#find the column name and add 4 - this is the width property
		loc = columnList.index(colName)+4
		#remove old width, add new width
		columnList.pop(loc)
		columnList.insert(loc, '450')
		
		#create tab delimited string
		columnStringNew = ""
		for i in range(len(columnList)):
			if 0 < i < len(columnList):
				columnStringNew += '\t'
			columnStringNew += columnList[i]
		
		#write to table
		self.defaultColumnView = columnStringNew
1 Like

Tangentially related ask, but given that there are so many options in this dropdown, is there any way to show the current value as the selected value on dropdown opening?

I found a couple of minutes to look at this again, and there was an order of operations issue. I've corrected it in the original code example. The problem was caused when the dropdown was already showing, and the script called for the dropdown to be shown again.

Changing:

if editorComponent.isShowing():
	editorComponent.setPopupVisible(True)
	setSizes(dropdown)

to

if not editorComponent.popupVisible and editorComponent.showing:
	editorComponent.setPopupVisible(True)
setSizes(dropdown)

corrects the issue. This way the script skips the setPopupVisible step if the popup is already visible. I also implemented @lrose's performance suggestions while I was at it.

Edit: I've implemented @PGriffith's performance suggestion as well

4 Likes