PyPopup Help

Vadus

pretend the impossible
Joined
Nov 23, 2003
Messages
374
Location
Northern Germany
Vadus said:
Here is a short tutorial to "Creating a popup and receiving data from it" : http://forums.civfanatics.com/showthread.php?p=3746502#post3746502


Hi all,

I'm trying to popup an EditBox and to receive it's value after pressing the popup ok button ( similar to the popup of city-renaming)
So I used the methods from the original CvEventManager refering to the city renaming. These are
__eventEditCityNameBegin()
Code:
def __eventEditCityNameBegin(self, city, bRename):
        popup = PyPopup.PyPopup(CvUtil.EventEditCityName, 
                            EventContextTypes.EVENTCONTEXT_ALL)
	popup.setUserData((city.getID(), bRename))
	popup.setHeaderString(localText.getText("TXT_KEY_NAME_CITY", ()))
	popup.setBodyString(localText.getText("TXT_KEY_SETTLE_NEW_CITY_NAME", ()))
	popup.createEditBox(city.getName())
	popup.setEditBoxMaxCharCount( 15 )
	popup.launch()

and __eventEditCityNameApply()
Code:
def __eventEditCityNameApply(self, playerID, userData, popupReturn):	
		'Edit City Name Event'
		iCityID = userData[0]
		bRename = userData[1]
		player = gc.getPlayer(playerID)
		city = player.getCity(iCityID)
		cityName = popupReturn.getEditBoxString(0)
		if (len(cityName) > 30):
			cityName = cityName[:30]
		city.setName(cityName, not bRename)

As you see, the Popup is created with a CvUtil.EventEditCityName
Argument. I think this internal Event manages the dataflow into the popup and back to the aggregating class.
Via a directory
Code:
self.Events={ CvUtil.EventEditCityName : 
                    ('EditCityName', self.__eventEditCityNameApply, 
                     self.__eventEditCityNameBegin)
}
a relation between the two methods and the EventEditCityName is made.
But I'm wondering if all this is needed to create just a Popup and receive the data from it. I need only the String of the popups EditBox. Arguments like in this example ( playerID , city , etc .. ) aren't required.
So I tried a little and got the popup to popup ;) by copying this example from the CvEventManager almost 1:1 to my test-mod. But I receive nothing after pressing the Ok-Button of the popup. Not even an error message :sad: So I think, that this CvUtil.EventEditCityName is to special for my case. As I said, I only want to receive the popup input in the aggregating class. So instead of a city, I just commit a None Object and so on .. This could be reason, why the CvUtil.EventEditCityName isn't working fine.
So is there an easier way to use a popup. Maybe without this CvUtil.EventEditCityName stuff ?
 
Ok, CvUtil.mumble is just a enum.

What happens is that when the popup is completed it generates a callback into the Civ4 engine. That callback is routed through the beginEvent call chain which uses its own internal handler map to get it to the apply function you have there. The begin functions are legacy to another implementation which they don't use, at least via the Event map, although some of the different clients call it directly.

Yes it is all necessary, if you think about thread execution and how UI works it'll make sense to you. When the popup is up, the computer is not simply sitting there waiting for you to type soemthing. Rather it polls (samples) for input while doing a myriad of other processing. When UI events trigger the popup's completion, it calls a function which was registered with it as a handler with the CyPopupReturn object for the dialog.

here's a snippet from my own custom CvEventManager class for doing this sort of thing.

Code:
class CvShinworkshopEventManager(CvEventManager.CvEventManager):
    def __init__(self):
        # initialize base class
        self.parent = CvEventManager.CvEventManager
        self.parent.__init__(self)
        self.CustomEvents={
            5021 : ('ShinPopup', self.__eventShinPopupApply, self.__eventShinPopupBegin),
            5022 : ('ShinPopup2', self.__eventShinPopup2Apply, self.__eventShinPopup2Begin),
        }
        
    def beginEvent( self, context, argsList=-1 ):
        'Begin Event'
        #entry = self.Events[context]
        #return entry[2]( argsList )
        if(self.CustomEvents.has_key(context)):
            return self.CustomEvents[context][2](argsList)
        else:
            self.parent.beginEvent(self, context, argsList)
            
    def applyEvent( self, argsList ):
        'Apply the effects of an event '
        context, playerID, netUserData, popupReturn = argsList

        if(self.CustomEvents.has_key(context)):
            entry = self.CustomEvents[context]
            return entry[1]( playerID, netUserData, popupReturn )   # the apply function
        else:
            return self.parent.applyEvent(self, argsList)

...

def __eventShinPopup2Apply(self, playerID, netUserData, popupReturn):
        pPlayer = gc.getPlayer(0)
        iX = pPlayer.getStartingPlot().getX()
        iY = pPlayer.getStartingPlot().getY()
        message = "In __eventShinPopup2Apply(%s)" %(str(popupReturn.getButtonClicked()))
        CyInterface().addMessage(0,True,len(message),message,'AS2D_BOMBARDED',0,'Art/Interface/Buttons/Units/Fighter.dds',ColorTypes(7),iX,iY,True,True)

You can find the enum in CvUtil.py if you feel like modding it directly. I'd leave CvEventManager.py alone, it's not necessary to mod it directly. Note, you could make your own generalized callback system and routing method in a generic handler for getting back simple info, but I haven't bothered to make one yet.
 
Belizan said:
Now why on earth did that code paste in so smurfed up'pedly... ah well, hopefully it's readable.

Use [ code ] instead of [ quote ]. Or use [ tab ] to add the tabs yourself if you for some strange reason don't like [ code ] :p
 
@Belizan : thanks for the answer ! :)

but could you post also your self.__eventShinPopup2Begin implementation ?
My question is, if it is enough to state the self reference as argument or if there is a special set of attributes required for this method header ?
And how is the relation between the event triggering GUI and this event-manager ? For example the city-rename : if I click on the city-text (which is implemented in the MainInterface.py ), the 'onCityRename' method of the EventManager is called, but no 'onCityRename' string can be found in MainInterface.py
 
I'm having a little bit of trouble understanding what you are saying. The begin method is never called by their infrastructure, despite being referenced in the Events mapping. It is sometimes called by various client modules which want to invoke that popup. The invocation to the popup whose Apply I including is...

Code:
note:
import Popup as PyPopup

...

            popup = PyPopup.PyPopup(5022, EventContextTypes.EVENTCONTEXT_ALL)
            message = "Your satellite has been successfully launched.\n\nHow will you task it??"
            popup.setBodyString( message )
            popup.addButton("Protect My Satellites")
            popup.addButton("Geosynchronous Orbit...")
            popup.addButton("Attack my enemy's satellites...")
            popup.launch(False, PopupStates.POPUPSTATE_QUEUED)

onCityRename is triggered by setName in the C++ layer, but it wouldn't be "onCityRename" you'd wanted to have looked for, but "cityRename", as those events are mapped as well.

The relation? is that when you call Popup.PyPopup you pass in a number which identifies the dialog box for the callback mechanism. This number coincides with those CvUtil.EventMumble "enums". The EventContexts allow you to distinguish between which resultant contexts should still produce a callback. Without these two parameters (i.e. if you take the defaults) no callback is generated.
 
I got an answer from my popup !! :eek: :banana: :band: :banana:

Belizan said:
The begin method is never called by their infrastructure, despite being referenced in the Events mapping.

Yes, but I have implement it at least to avoid an Attribute Error ;)

It confuses me a bit, that I have to implement the method to setup a popup twice. Once in my special screen class (which triggers the popup from its handleInput method) and once in the CvCustomEvent class.


But one last question :

With my popup I want to get a string, I have entered in the popups EditBox.
So here is my popup creating method from my screen class :

Code:
    def __eventEditUnitRename(self, iUnitName):
        popup = PyPopup.PyPopup(5001, EventContextTypes.EVENTCONTEXT_ALL)
        popup.setHeaderString("Unit Name")
        popup.setBodyString("TestUnitBody")
        popup.createEditBox()
        popup.setEditBoxMaxCharCount( 15 )
        popup.launch()

and here the __eventShinPopup2Apply from you :
Code:
	def __eventShinPopup2Apply(self, playerID, netUserData, popupReturn):
		pPlayer = gc.getPlayer(0)
		iX = pPlayer.getStartingPlot().getX()
		iY = pPlayer.getStartingPlot().getY()
		
		message = "In __eventShinPopup2Apply(%s)"
                           %(str(popupReturn.getButtonClicked()))
		CyInterface().addMessage(
                    0,True,len(message),message,'AS2D_BOMBARDED',0,
                    'Art/Interface/Buttons/Units/Fighter.dds',
                    ColorTypes(7),iX,iY,True,True
                 )

So do I have to give one or more special parameters to the popups launch method ? Or is it just a method of the popupReturn Object ? And what class is popupReturn actually? Are the methods of popupReturn documented somewere ?
 
popupReturn is a CyPopupReturn object (you thought I made that up? 8). It is in that gray not so documented part of the API. I have my own forensics coding tools which I used to rationalize the two API documentation sources which I use--
http://civilization4.net/files/modding/PythonAPI/
http://www.sthurlow.com/cvDocs/cvDoc/CyPopupReturn.htm

As for popup.launch(), it depends on what you want to do. The default parameters will work, but may not behave as you might like.

I'm not sure at all about what you are imlementing twice. There are two pieces to making a popup, but they are separate and distinct, performing very different tasks. One configure the popup and brings it into being. The other processes the results of that dialog box--the accumulated feedback from the user. Totally different tasks with no overlap.

I assume you are not using those two functions literally as you have them there. Neither is correct for what you want to do. Your popup can not use the same identifying value as the rename city dialog or either your dialog or the edit city dialog will cease to function properly. In the apply, you want to get the editbox's value, not a button result, which means calling popupReturn.getEditBoxString(0).
 
hmm..
popupReturn.getEditBoxString(0) .. sounds familiar ..

My first post !!
Code:
def __eventEditCityNameApply(self, playerID, userData, popupReturn):	
		'Edit City Name Event'
		iCityID = userData[0]
		bRename = userData[1]
		player = gc.getPlayer(playerID)
		city = player.getCity(iCityID)
		[b]cityName = popupReturn.getEditBoxString(0)[/b]
		if (len(cityName) > 30):
			cityName = cityName[:30]
		city.setName(cityName, not bRename)

allright, and yeah .. the __eventShinPopup2Begin method has really nothing to do with my __eventEditUnitRename. Late cognition from me, sorry :mischief:

I will sum up the way to create a popup and to receive data from it for all the other readers in some moments. Many many thanks Belizan :)
 
So, to all readers, wondering how this issue might work :

First of all, you have create your popup wherever you want (mostly in a screen class). You can do this with a function like :

Code:
    def __eventMyPopup(self):
        popup = PyPopup.PyPopup(9876, EventContextTypes.EVENTCONTEXT_ALL)
        popup.setHeaderString("Popup Header")
        popup.setBodyString("Popup Body Line")
        popup.createEditBox()
        popup.launch()

You see, that an integer param is given to the PyPopup contructor.
This is the ID, which the engine uses to refer from the popup to the event method, which handles the popup return values, after you have clicked on "OK" at the popup.
This implementation is done in your CvCustomEventManager class.
Here you have to define a directory attribute in the __init__ method :

Code:
self.CustomEvents={
    9876 : ('MyPopupEvent', self.__eventMyPopupApply, self.__eventMyPopupBegin)
        }

When the ok-button of the popup was pressed the following things happen
Belizan said:
What happens is that when the popup is completed it generates a callback into the Civ4 engine. That callback is routed through the beginEvent call chain which uses its own internal handler map to get it to the apply function you have there. The begin functions are legacy to another implementation which they don't use, at least via the Event map, although some of the different clients call it directly.

So, in the EventManager beginEvent and applyEvent are called.
To use your own directory CustomEvents you have to overwrite these methods in your CvCustomEventManager :

Code:
	def beginEvent( self, context, argsList=-1 ):
		"Begin Event"
		if(self.CustomEvents.has_key(context)):
			return self.CustomEvents[context][2](argsList)
		else:
			self.parent.beginEvent(self, context, argsList)
            
	def applyEvent( self, argsList ):
		'Apply the effects of an event '
		context, playerID, netUserData, popupReturn = argsList
		
		if(self.CustomEvents.has_key(context)):
			entry = self.CustomEvents[context]
                        # the apply function
			return entry[1]( playerID, netUserData, popupReturn )   
		else:
			return self.parent.applyEvent(self, argsList)

You see, if an event was triggered, the begin and apply methods look up the event-ID at first in your own directory and if the ID isn't found, the according methods of the parental EventManager will be called.

At last you have to implement the __eventMyPopupApply and __eventMyPopupBegin in the CvCustomEventManager

Code:
	def __eventMyPopupBegin(self):
		pass
           
	def __eventMyPopupApply(self, playerID, netUserData, popupReturn):
		pPlayer = gc.getPlayer(0)
		iX = pPlayer.getStartingPlot().getX()
		iY = pPlayer.getStartingPlot().getY()
		message = "In __eventShinPopup2Apply(%s)"
                           %(str(popupReturn.getButtonClicked()))
		CyInterface().addMessage(
                       0,True,len(message),message,'AS2D_BOMBARDED',0,
                       'Art/Interface/Buttons/Units/Fighter.dds',
                       ColorTypes(7),iX,iY,True,True)
                CyInterface().addImmediateMessage(
                     "Popup Input : %s" %(popupReturn.getEditBoxString(0)),"")
                )

The __eventMyPopupBegin method isn't needed, but, however, has to be stated at the CustomEvent directory, and so it has also to be implemented.
Belizan says about that :

Belizan said:
The begin method is never called by their infrastructure, despite being referenced in the Events mapping. It is sometimes called by various client modules which want to invoke that popup.

So, let this function be..
And thats it ! No you should see, what you have entered in the popup, on the left side of your Civ4 screen, after pressing the popup-Ok :cool:
 
Truth be told you could leave the begin method out entirely and rewrite the beginEvent and applyEvent functions to ignore it, but I was being lazy at the time (and hadn't convinced myself it truely was never called). Experimentation has proven the latter, and logic therefore dictates the former 8). But the above will work! :)

P.S. It's also worth mentioning that your popup identifier (that number you used while creating the popup) should be > 5010.
 
Hi,

has anyone already made a popup with table ?

With that I get a popup, but a table isn't shown :

Code:
        popup = PyPopup.PyPopup(5002, EventContextTypes.EVENTCONTEXT_ALL)
        popup.setHeaderString("Choose a Cell")
        popup.createTable(2, 2)
        cellPos = [0, 0]
        for k in range(4):
            popup.addTableCellText(cellPos[0], cellPos[1], "testCell")
            if ((k+1)%2 == 0):
                cellPos[0] = cellPos[0] + 1
                cellPos[1] = 0
            else:
                cellPos[1] = cellPos[1] + 1
                    
        popup.completeTableAndAttach(0, 0)
        popup.launch()

.. why ? :mischief:

Actually I want to add a list (or table) of Promotion-ImageButtons to the Popup. Is this possible by writing a new popup class which inherits from Popup ? (because the civ4 PopupClass has no : addImageButton( ... ) method )
However with or without image, this button should deliver the ID of the promotion. How can this be done ??
 
Top Bottom