Howto: Advanced Popups with controls (and PIE!)

Del69

Chieftain
Joined
Oct 6, 2008
Messages
34
Location
Covington, WA
First off if you just want some text and a yes/no or an ok button I'd recommend using Kael's excellent writeup on using CyPopupInfo() style popups which are much simpler and perfect for that thing. Its over here How To: Add Popups If you need a bit more from your popups dive into the wall of text below at your own risk...

Advanced Popup Events

Demo -
Go into game, open up the python console and put the following in (shift-`) on a us keyboard, not just ` which is the debug console. The right one has a >> prompt and prints out the python version info when you open it.

Code:
import CvPopupInterface
CvPopupInterface.initTestPopup()

Thats one included with the original civ4 that shows all the controls. And if yours is like mine the radio buttons don't work and I have no idea why :D

Initial config:

Using bug event manager:
Go ahead and read BUG Events for the docs on how events are defined, you'll be a better person for it ...really...i swear...docs are good mm'kay.
Code:
import CvEventInterface

ADDSIGN_EVENT_ID = CvUtil.getNewEventID("OverlayAddSign")

CvEventInterface.getEventManager().setPopupHandler(ADDSIGN_EVENT_ID, ("OverlayAddSign", applyAddSignEvent, beginAddSignEvent))

The first line gives your event a unique ID number that the game uses to call the correct functions when its opened and closed.

The second line gets the current event manager and tells it to store a handler for the ID number ADDSIGN_EVENT_ID, call it "OverlayAddSign", and call the functions beginAddSignEvent when the event begins, and applyAddSignEvent when the popup returns (usually from an ok button click).

Ok so now you have an event ready to fire, so what good does that do you? Well lets say your somewhere in your code and you want this popup to appear. A simple call to
Code:
CyEventInterface.getEventManager().beginEvent(ADDSIGN_EVENT_ID, (argument1,argument2))
and your event goes off.

So whats (argument1,argument2)? Whatever you want them to be. The call to beginevent takes a second argument that defaults to -1 if nothing is put there, but its so you can put in your own data to carry through to your event handlers and popups.

in this example the actual call looks like this
Code:
CyEventInterface.getEventManager().beginEvent(ADDSIGN_EVENT_ID, (self.currentPlotX, self.currentPlotY))
The 2 variables are ones tracked in the object that its called from.

Now we get to the meat of the matter, just how does this help me get a popup going? Well i'm lazy so i'm just going to post up the code I used for my popups :D

Code:
def beginAddSignEvent(argsList):
	"""
	Starts the popup for the OverlayAddSign Event
	"""
	localTxt = CyTranslator()
	artMgr = CyArtFileMgr()
	hdrTxt = localTxt.getText("TXT_KEY_STRATOVERLAY_ADDSIGN_TITLE",())
	bodyTxt = localTxt.getText("TXT_KEY_STRATOVERLAY_ADDSIGN_BODY",())
	editboxHelp = localTxt.getText("TXT_KEY_STRATOVERLAY_ADDSIGN_EDITBOX_HELP",())

	popup = CyPopup(ADDSIGN_EVENT_ID, EventContextTypes.EVENTCONTEXT_ALL,True)
	popup.setUserData( argsList )
	popup.setHeaderString(hdrTxt, CvUtil.FONT_CENTER_JUSTIFY)
	popup.setBodyString(bodyTxt, CvUtil.FONT_LEFT_JUSTIFY)

	popup.createPythonEditBox("", editboxHelp, GROUP_SIGNTXT)
	popup.launch(True, PopupStates.POPUPSTATE_MINIMIZED)
Ok here is your function that is called immeadiatly after the event fires.

the first few lines are just getting some text out of the translator to show in the popup. I'll start down at
Code:
	popup = CyPopup(ADDSIGN_EVENT_ID, EventContextTypes.EVENTCONTEXT_SELF,True)
Here you create your popup object, note its a CyPopup object which allows quite a few controls to be used with it.

The first parameter is the popup ID, which is what you got with the getNewEventID at the beginning.

The second is the event context which can be NO_EVENTCONTEXT, EVENTCONTEXT_SELF, EVENTCONTEXT_ALL. The event context is the event that is currently being processed by the event manager from what I can tell. Most likely this is so it won't popup if its not in the event context its defined for, since you are calling it manually its always going to be in its own context when it pops up though, so this one is kinda trivial for this usage. Setting it to EventContextTypes.EVENTCONTEXT_SELF is probably the best practice here.

The last parameter is True for dynamic or false for Not, what it does I have no idea, doesn't seem to make any difference.
Code:
	popup.setUserData( argsList )
This is where you can pass your arguments you passed into the call to beginEvent along the chain. Anything passed to setUserData will pass along the event chain with the popup as an argument in your apply function call. In this case I don't do anything to the arguments just pass them on but you can modify them however you want in this function before sending them with the popup.
Code:
	popup.setHeaderString(hdrTxt, CvUtil.FONT_CENTER_JUSTIFY)
	popup.setBodyString(bodyTxt, CvUtil.FONT_LEFT_JUSTIFY)
Put in some nice header and body text. For some reason the Justification options don't seem to work here but thats how all the examples I found used it.

Code:
	popup.createPythonEditBox("", editboxHelp, GROUP_SIGNTXT)
Here we create a text box to type into. In this case i'm creating a box that has nothing in it to start, putting "Jimbob" in here as the first parameter would have the text "Jimbob" already in the box the user types into.

The second parameter is the help text to show when you hover over the control with the mouse.

The last one is a group number, you'll need this when you go to get the values out of the popup when it returns later. In this case GROUP_SIGNTXT is a constant I defined for 0.

For all the control creation functions there is 2 versions, a python version and a non-python version. Mostly the python versions have more functionality, like they take a help text for when you hover over the control. Or the buttons take an optional image file or a function to call when its clicked. Check out CvPopupInterface.py in the Assets/Python/EntryPoints folder and the Python API Docs under CyPopup for a full list of the controls. Any button you put on the popup will close it when clicked.

Code:
	popup.launch(True, PopupStates.POPUPSTATE_MINIMIZED)
And finally we show our popup to the user. The first parameter is whether you want the engine to create an ok button at the bottom of the popup for you. If you had added a button somewhere on the popup you would probably set this to false.

ss of this particular popup:
popupmin.jpg


Well where is it? You made me read all this for that? :mad:

See the ? in the corner, remember we told it to popup minimized. Heres the version of it expanded:

Spoiler :
popupshow.jpg


Now we wait, go have a soda and a smoke while the user gasps in awe at our popup of awsomeness and sometime in the future when they manage to tear their eyes off of its glory and click the ok button.

Ok so slight bit of exaggeration there :shifty:

That brings us down to this function:
Code:
def applyAddSignEvent(playerID, userData, popupReturn):
	"""
	Applys the popup for OverlayAddSign Event
	"""
	signText = popupReturn.getEditBoxString(GROUP_SIGNTXT)
	signText = CvUtil.convertToStr(signText)
	x,y = userData
	if gc.getMap().isPlot(x,y):
		plot = gc.getMap().plot(x,y)
		player = gc.getGame().getActivePlayer()
		CyEngine().addSign(plot, player, signText)
		overlayScreen.saveSign(x, y, signText, player)

So the parameters passed in to start are the playerID of the player who recieved the popup i'm assuming. This gets set somewhere along the line I haven't figured out where yet nor am I sure of the value so feel free to expiriment with it.

Second is the userdata we set back in the begin function with setUserData.

Third is a CyPopupReturn object that gives you all the info about what the user did with your popup.

Code:
	signText = popupReturn.getEditBoxString(GROUP_SIGNTXT)
So you remember the group value, well here is where you use it. Each of the functions in CyPopupReturn take a group value and pass back out a value depending on what was done in the popup. There is one for Each type of widget in the popups that return a value, except for the poor check box, not sure on what happens to those or if the function is just missing from the api docs. The rest of the function is just extracting out the data I stored earlier and using it to plop a sign down.
Code:
	signText = CvUtil.convertToStr(signText)
	x,y = userData
	if gc.getMap().isPlot(x,y):
		plot = gc.getMap().plot(x,y)
		player = gc.getGame().getActivePlayer()
		CyEngine().addSign(plot, player, signText)
		overlayScreen.saveSign(x, y, signText, player)

And thats all folks, your wonderful guide to using popups with controls.

If you want to do this without using the bug mod and its event manager facilities, you need to make just a couple changes.

Instead of
Code:
ADDSIGN_EVENT_ID = CvUtil.getNewEventID("OverlayAddSign")

Go to the top of CvUtil and put in:
Code:
ADDSIGN_EVENT_ID = 6000
where the 6000 is some number not used in the events listed there.

Instead of calling:
Code:
CvEventInterface.getEventManager().setPopupHandler(ADDSIGN_EVENT_ID, ("OverlayAddSign", applyAddSignEvent, beginAddSignEvent))
from your function, open up CvEventManager and about line 141 add your event into the Dictionary self.Events there like this:
Code:
self.Events={
			CvUtil.EventEditCityName : ('EditCityName', self.__eventEditCityNameApply, self.__eventEditCityNameBegin),
			CvUtil.EventEditCity : ('EditCity', self.__eventEditCityApply, self.__eventEditCityBegin),
			CvUtil.EventPlaceObject : ('PlaceObject', self.__eventPlaceObjectApply, self.__eventPlaceObjectBegin),
			CvUtil.EventAwardTechsAndGold: ('AwardTechsAndGold', self.__eventAwardTechsAndGoldApply, self.__eventAwardTechsAndGoldBegin),
			CvUtil.EventEditUnitName : ('EditUnitName', self.__eventEditUnitNameApply, self.__eventEditUnitNameBegin),
			CvUtil.EventWBAllPlotsPopup : ('WBAllPlotsPopup', self.__eventWBAllPlotsPopupApply, self.__eventWBAllPlotsPopupBegin),
			CvUtil.EventWBLandmarkPopup : ('WBLandmarkPopup', self.__eventWBLandmarkPopupApply, self.__eventWBLandmarkPopupBegin),
			CvUtil.EventWBScriptPopup : ('WBScriptPopup', self.__eventWBScriptPopupApply, self.__eventWBScriptPopupBegin),
			CvUtil.EventWBStartYearPopup : ('WBStartYearPopup', self.__eventWBStartYearPopupApply, self.__eventWBStartYearPopupBegin),
			#add this part
			CvUtil.ADDSIGN_EVENT_ID : ('OverlayAddSign', self.applyAddSignEvent, self.beginAddSignEvent),
			#end adding part
		}

then add your popup functions at the bottom of the file. Make sure to put the self. part on the function names since they will be part of the CvEventManager class when done this way. You could also add an import statement to the top of the file and reference your event functions that way too.

Other than that everything else is the same besides specific to whatever your doing with it.

Hope this helps some people out.

Oh and I lied about the pie that was just to draw you in :evil:
 
Back
Top Bottom