Jython: Setting up a Popup Menu dynamically

I am having some trouble on the Jython end of creating a popup menu. The underlying goal (while perhaps not the best programming practice) is to have each item’s action perform a Jython command which is stored as a string in the database. I have a function called ‘generalFunction’ which is supposed to retrieve a string from the database depending on which item was selected. The problem is that when I setup the dictionary sequence to populate the popup (in the format of {‘Menu Text’:functionToCall} ) I cannot pass a parameter into my functionToCall (in my case it is generalFunction). If I define the action for the item to be generalFunction(idx), the function executes immediately which I do not want. I just want to assign a different instance of this function to each item in the popup menu.

Is there anyway to setup a function that can reference another function as a variable? Would I be better off going about this at the Java level and using the Java JMenuPopup documentation? Thanks in advance!

[code]qryText = “SELECT idx, popupText FROM %s WHERE id = %i AND roleId =%i” %(popupCfgTable, depth, roleId)
popupData = system.db.runQuery(qryText, dbname)

def generalFunction(idx=idx, event=event, selectedId=selectedId, selectedParentId=selectedParentId, popupCfgTable=popupCfgTable, depth=depth):
import system
import app
qryNames = “SELECT functionName FROM %s WHERE idx = %i” %(popupCfgTable, idx)
functionName = system.db.runScalarQuery(qryNames, dbname)
exec functionName

Obtain the unique id and popup message text based on the depth of the selection and the user’s role

popSequence = {}
for p in popupData:
idx = p[0]
popupText = p[1]
popSequence[popupText] = generalFunction

popSequence[popupText] = generalFunction(idx)

menu = system.gui.createPopupMenu(popSequence)
menu.show(event)[/code]

You could use the eval function to make dynamic function calls from strings.

Here’s a test case I created:

Create a Script Module (app.popupmenu):

def testMyFunction(TextToDisplay):
	import system
	system.gui.messageBox(TextToDisplay)

def callIt(myFunc):
	import app
	eval(myFunc + "(\"Hello, World\")")

Create a window and add a button (Root Container.Button).
Right click the button and go to Event Handlers.
On left, click + by action then click actionPerformed.
Click tab Script Editor:

app.popupmenu.callIt("app.popupmenu.testMyFunction")

Click OK.
Click the green PREVIEW arrow (see attached image).
Click BUTTON.

See “Hello, World”

[quote=“adamaustin”]You could use the eval function to make dynamic function calls from strings.

Here’s a test case I created:

Create a Script Module (app.popupmenu):

def testMyFunction(TextToDisplay):
	import system
	system.gui.messageBox(TextToDisplay)

def callIt(myFunc):
	import app
	eval(myFunc + "(\"Hello, World\")")

Create a window and add a button (Root Container.Button).
Right click the button and go to Event Handlers.
On left, click + by action then click actionPerformed.
Click tab Script Editor:

app.popupmenu.callIt("app.popupmenu.testMyFunction")

Click OK.
Click the green PREVIEW arrow (see attached image).
Click BUTTON.

See “Hello, World”[/quote]

Thanks for the reply. Unfortunately using eval yielded the same results. The function gets executed immediately.

I’ve also tried using “lambda” to create an anonymous function that calls my generalFunction function but when I do that the generalFunction goes out of scope (error says that function isn’t defined).

I’ve been reading up on callback functions but haven’t found any that give a clear example as to what is going on.

For now you can just move the “def” of the general function (with idx=idx) inside the loop so it gets redefined for each unique index.

Well, I know this doesn’t exactly help you now, but in 7.6 you’ll be able to do something like this:

from functools import partial

def f1(event):
	print 'f1(event=%s)' % str(event)
	
def f2(event):
	print 'f2(event=%s)' % str(event)

def f3(event, param):
	print 'f3(event=%s, param=%s)' % (str(event), str(param))
	 
	 
f3_partial = partial(f3, param='some value')

menu = system.gui.createPopupMenu({
	'item1' : f1, 
	'item2' : f2, 
	'item3' : f3_partial})
	
menu.show(event)

This is valid Jython code in 7.5 as well, but because of some internal scripting details it won’t work for this particular case.

Thanks Carl I think I may try that. I actually originally came back to this thread to post a solution I found. Essentially I globally stored the DB table’s unique column (idx) and the function to be called when a popup is clicked (generalFunction). Wasn’t too thrilled about this solution, but it works.

[code]qryText = “SELECT idx, popupText FROM %s WHERE id = %i AND roleId =%i” %(popupCfgTable, depth, roleId)
popupData = system.db.runQuery(qryText, dbname)
for p in popupData:
idx = p[0]
popupText = p[1]
#system.gui.messageBox(“idx=%i” %(idx))

def generalFunction(idx, event=event, selectedId=selectedId, selectedParentId=selectedParentId, popupCfgTable=popupCfgTable, depth=depth):
import system
import system
import app
# Obtain the function to be called from the database, looked up by unique id (idx).
qryNames = “SELECT functionName FROM %s WHERE idx = %i” %(popupCfgTable, idx)
functionName = system.db.runScalarQuery(qryNames, dbname)
# Call the command obtained from the DB
exec functionName

Prepare the dictionary for the popup configuration

popSequence = {}

Setup horrible solution of using global variables

global tmp_lambda
global tmp_gf
tmp_lambda = 0

Populate the dictionary and setup calls to generalFunction

for p in popupData:
idx = p[0]
popupText = p[1]
tmp_idx = idx
tmp_gf = generalFunction
gf = lambda x : tmp_gf(tmp_idx)
popSequence[popupText] = gf

menu = system.gui.createPopupMenu(popSequence)
menu.show(event)

[/code]

Nothing like putting a band-aid on the problem.

Just an update, the above solution did not work. All the parameters for the function seemed to be passed by reference rather than value. The solution was to actually add a callback function. I also noticed a bug where the callback function required the event to be the first parameter. I’m not sure why and it thew me through a loop for a while. I also had to use ‘eval’ instead of ‘exec’ since you are apparently not allowed to use exec inside a callback that uses ‘free variables’.

Long story short, this works:

[code]qryText = “SELECT idx, popupText FROM %s WHERE id = %i AND roleId =%i ORDER BY idx ASC” %(popupCfgTable, depth, roleId)
popupData = system.db.runQuery(qryText, dbname)
for p in popupData:
idx = p[0]
popupText = p[1]

def generalFunction(event,idx, selectedId , selectedParentId, depth, popupCfgTable):
import system
import system
import app

def callback(event, idx=idx,  selectedId=selectedId , selectedParentId=selectedParentId, depth=depth, popupCfgTable=popupCfgTable):
	import app
	import system

	qryNames = "SELECT functionName FROM %s WHERE idx = %i " %(popupCfgTable, idx)
	functionName = system.db.runScalarQuery(qryNames, dbname)      
	eval(functionName)
									
return callback		  

Prepare the dictionary for the popup configuration

popSequence = {}

for p in popupData:
idx = p[0]
popupText = p[1]
gf = generalFunction(event, idx, selectedId , selectedParentId, depth, popupCfgTable)
popSequence[popupText] = gf

Create and show the popup

menu = system.gui.createPopupMenu(popSequence)
menu.show(event)

[/code]

Looking forward to 7.6’s support of the partial package.

7.5 supports partial() too, but the createPopupMenu() function doesn’t know how to call the object returned by partial() in 7.5.

Feel free to use it in other places, if appropriate, though.