The latest version of this document can be found at www.broad.ology.org.uk/amiga/proaction/05_A_Simple_App.html
1: #!python
2: #
3: ################################################################
4: #
5: # Anatomy of a ProAction Application.
6: #
7: # 05_A_Simple_App.py
8: #
9: # Extended and customising our Application Class
10: # Using the getfile.gadget
11: # Combining the various elements into a nmore advance GUI
12: #
13: ################################################################
14: #
15: #
16:
17: import sys
18: import os
19: import arexx
20:
21: class Application:
22:
23: def __init__(self):
24:
25: self.PORTNAME = "MakePDF"
26: self.UNIQUE = True
27: self.selected = "2"
28: self.command = "GHOSTSCRIPT:gs -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress -sPAPERSIZE=%s -dNOPAUSE -dBATCH -dQUIET "-sOutputFile=%s" "%s" "
29:
30: # Disable all our gadgets
31: def disable(self, guikey):
32: arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + self.cnlist_gid + " TAGSTRING "GA_Disabled,1,TAG_DONE"")
33: arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + self.convert_gid + " TAGSTRING "GA_Disabled,1,TAG_DONE"")
34: arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + self.getPSfile_gid + " TAGSTRING "GA_Disabled,1,TAG_DONE"")
35: arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + self.getPDFfile_gid + " TAGSTRING "GA_Disabled,1,TAG_DONE"")
36:
37: # Enable all our gadgets
38: def enable(self, guikey):
39: arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + self.cnlist_gid + " TAGSTRING "GA_Disabled,0,TAG_DONE"")
40: arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + self.convert_gid + " TAGSTRING "GA_Disabled,0,TAG_DONE"")
41: arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + self.getPSfile_gid + " TAGSTRING "GA_Disabled,0,TAG_DONE"")
42: arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + self.getPDFfile_gid + " TAGSTRING "GA_Disabled,0,TAG_DONE"")
43:
44:
45: # Parse the ghostscript statd script to determine
46: # available page sizes
47:
48: def GetPageSizeList():
49:
50: statd_filename = "GHOSTSCRIPT:Data/8.54/lib/gs_statd.ps"
51:
52: statd_handle = open(statd_filename,'r')
53:
54: if statd_handle:
55: found_sizes = False
56: page_size_list = []
57: for line in statd_handle:
58: if found_sizes:
59: if line[:10] == "%END SIZES":
60: break
61: line = line.strip()
62: if line[:1] != "/":
63: continue
64: parts = line.split()
65: page_size = parts[0].lstrip('/')
66: if parts[1][0] != "/":
67: page_size_list.append(page_size)
68: if line[:13] == "/.setpagesize":
69: found_sizes = True
70: statd_handle.close()
71: return page_size_list
72:
73:
74: # Certain gadgets such as Listbrowsers, Choosers, RadioButtons etc require
75: # lists of nodes of elements in the gadget to be built in advance
76: # ProAction provides functions to deal with these based on the Exec functions.
77:
78: def MakeChooserNodeList(guikey, *strings):
79: concstrings = []
80: # First we need an exec list to hold our ChooserNodes
81: # We create this with a special command NEWGUIOBJECT and specify an object
82: # Of type GUIOBJ_List . An ID for the list object will be returned that
83: # We can use in all further operations on it.
84:
85: (rc,rc2,cnlist) = arexx.dorexx("PROACTION","NEWGUIOBJECT GUIID " + guikey + " OBJECTTYPE "GUIOBJ_List"")
86: # Before proceding verify the allocation above worked, else things may go badly
87: if rc == 0:
88: # Our function recives a tuple in the variable strings, which may consist of
89: # seperate strings or lists of strings, depening on how we call it.
90: # We need a (python) list of strings so this next assembles that for us.
91:
92:
93: for string in strings:
94: if not isinstance(string,list):
95: concstrings.append(string)
96: else:
97: concstrings.extend(string)
98:
99: # Now we process the list of strings we have
100:
101: for string in concstrings:
102: # Build atag list for a new node.
103: cntags = ""
104: cntags += "CNA_Text," + str(string) + ","
105: cntags += "TAG_DONE"
106: # The command ALLOCCHOOSERNODE is a Wrapper for the AlloChooserNode() C
107: # function. It returns an ID for a GUIOBJ_Node Object which we can add to the
108: # growing choosernode list.
109:
110: (rc,rc2,cn) = arexx.dorexx("PROACTION","ALLOCCHOOSERNODE GUIID " + guikey + " TAGSTRING "" + cntags + """)
111: if rc == 0:
112: # verify our success and add to the tail of the list with teh ADDTAIL command
113: arexx.dorexx("PROACTION", "ADDTAIL GUIID " + guikey + " LISTID " + cnlist + " NODEID " + cn )
114:
115: return cnlist
116:
117: # Our choosernodes are not automatically released at script exit so we need to
118: # Clean up the list. The list it self will be freed on automatically on exit.
119:
120: def FreeChooserNodeList(guikey, rbnlist):
121: # We remove each node in turn using the REMTAIL command.
122: (rc, rc2, rbn) = arexx.dorexx("PROACTION","REMTAIL GUIID " + guikey + " LISTID " + rbnlist)
123: while rc == 0:
124: # if succesful we pass the NODEID to the FREECHOOSERNODE command.
125: arexx.dorexx("PROACTION","FREECHOOSERNODE GUIID " + guikey + " NODEID " + rbn)
126: # remove next node and loop.
127: (rc, rc2, rbn) = arexx.dorexx("PROACTION","REMTAIL GUIID " + guikey + " LISTID " + rbnlist)
128:
129:
130: #
131: # HandleInput() receives commands at the ARexx port and acts accordingly.
132: # They may be commands from ProAction (the CLOSE command in this example)
133: # Or they maybe commands from other ARexx based scripts etc.
134: #
135:
136: def HandleInput(pyport,guikey):
137:
138: global app
139:
140: # Loop until die becomes non zero
141: die = 0
142: while die == 0:
143: # Wait for a message to arrive at our Port
144: pyport.wait()
145: # Get the first message (but there may be more than one)
146: msg = pyport.getmsg()
147: # Loop until all queued messages are processed
148: while msg:
149: cmd = msg.msg
150: msg.reply()
151: if cmd == "QUIT":
152: die = 1
153: break
154: elif cmd[:5] == "CLOSE":
155: # Window Close Button Pressed
156: die = 1
157: break
158: elif cmd[:8] == "GADGETUP":
159: # The format of this command is
160: # GADGETUP GUIID guikey GADGETID gid CODE code
161: # We uses the split() method of the python string object to neatly parse this.
162: # The value lguikey would allow us to deal with multiple GUI windows.
163: (dummy,dummy,lguikey,dummy,gid,dummy,code) = cmd.split()
164: if gid == app.convert_gid:
165: pagesize = app.pagesizelist[int(app.selected)]
166:
167: # Use the GETATTR command to determine the chosen filenames
168:
169: (rc,rc2,PSfile) = arexx.dorexx("PROACTION","GETATTR GUIID " + guikey + " OBJECTID " + app.getPSfile_gid + " TAGNAME "GETFILE_File"")
170: (rc,rc2,PSdrawer) = arexx.dorexx("PROACTION","GETATTR GUIID " + guikey + " OBJECTID " + app.getPSfile_gid + " TAGNAME "GETFILE_Drawer"")
171:
172: # Use the os.path API to cobine the drawer and file names for maximum portabilty
173: # and ease.
174: PSpath = os.path.join(PSdrawer,PSfile)
175:
176: # Repeat for PDF destination
177:
178: (rc,rc2,PDFfile) = arexx.dorexx("PROACTION","GETATTR GUIID " + guikey + " OBJECTID " + app.getPDFfile_gid + " TAGNAME "GETFILE_File"")
179: (rc,rc2,PDFdrawer) = arexx.dorexx("PROACTION","GETATTR GUIID " + guikey + " OBJECTID " + app.getPDFfile_gid + " TAGNAME "GETFILE_Drawer"")
180: PDFpath = os.path.join(PDFdrawer,PDFfile)
181:
182: # This builds our command line from the template defined in the class definition above
183:
184: command = app.command % (pagesize,PDFpath, PSpath)
185:
186: # Disable the GUI whilst working
187: app.disable(guikey)
188:
189: # Call the external command. This could fail but it's failure won't interrup our script.
190:
191: os.system(command)
192:
193: # Enable the GUI.
194:
195: app.enable(guikey)
196:
197: elif gid == app.cnlist_gid:
198: app.selected = code
199: elif gid == app.getPSfile_gid:
200: (rc,rc2,chosen) = arexx.dorexx("PROACTION","REQUESTFILE GUIID " + guikey + " OBJECTID " + app.getPSfile_gid)
201: elif gid == app.getPDFfile_gid:
202: (rc,rc2,chosen) = arexx.dorexx("PROACTION","REQUESTFILE GUIID " + guikey + " OBJECTID " + app.getPDFfile_gid)
203: else:
204: print cmd
205: msg = pyport.getmsg()
206:
207:
208: def DoGUI(pubscreen):
209:
210: global app
211:
212:
213: # Our script needs an incoming ARexx port to receive messages from
214: # ProAction. We create this using the arexx modules Port method
215: # Which returns a Port object
216:
217:
218:
219: pyport = arexx.Port(app.PORTNAME)
220: if pyport:
221: if app.UNIQUE:
222: if pyport.name != app.PORTNAME:
223: ErrorExit("ARexx Port " + app.PORTNAME + " already Exists!")
224:
225: # First we'll build our window.class tagslist
226: # Python can't use send StemVars at the moment so we'll
227: # use the TAGSTRING tecnique throughout.
228:
229: wintags = ""
230: wintags += "WA_Width,300,"
231: wintags += "WA_Height,100,"
232: wintags += "WA_DragBar,1,"
233: wintags += "WA_DepthGadget,1,"
234: wintags += "WA_SizeGadget,1,"
235: wintags += "WA_CloseGadget,1,"
236: wintags += "WA_Title,"+ sys.argv[0] + ","
237: wintags += "WA_PubScreenFallBack,1,"
238: wintags += "WA_PubScreenName," + pubscreen + ","
239: wintags += "WINDOW_Position,WPOS_CENTERSCREEN,"
240: wintags += "WINDOW_LockHeight,1,"
241: wintags += "WA_Activate,1,"
242: wintags += "TAG_DONE"
243:
244: # Now a taglist to define our top level layout.gadget
245:
246: layouttags = ""
247: layouttags += "LAYOUT_Orientation,LAYOUT_ORIENT_VERT,"
248: layouttags += "LAYOUT_HorizAlignment,LALIGN_CENTER,"
249: layouttags += "TAG_DONE"
250:
251: # Tags for our label image.
252: labeltags = ""
253: labeltags += "LABEL_Justification,LJ_CENTER,"
254: # Notice in this next line we pass the newline as "*N" this is because the
255: # ProAction commands are parsed with ReadArgs, so muxt be "DOS Encoded"
256: labeltags += "LABEL_Text,PDF Maker*NPlease choose source and destination file names,"
257: labeltags += "TAG_DONE"
258:
259: # tags for two getfile gadgets, for the source and destination.
260: # NB the gadget is set to read only as we don't have the abilty to
261: # define a hook for it with ProAction at present.
262: # But we will activate the reuester via the REQUESTFILE command when the GUI is running
263:
264: getPSfiletags = ""
265: getPSfiletags += "GETFILE_TitleText,Select Postscript Source,"
266: getPSfiletags += "GETFILE_DoSaveMode,0,"
267: getPSfiletags += "GA_RelVerify,1,"
268: getPSfiletags += "GETFILE_ReadOnly,1,"
269: getPSfiletags += "GETFILE_DoMultiSelect,0,"
270: getPSfiletags += "GETFILE_Drawer,RAM:,"
271: getPSfiletags += "GETFILE_File,ww.ps,"
272: getPSfiletags += "TAG_DONE"
273:
274:
275: getPDFfiletags = ""
276: getPDFfiletags += "GETFILE_TitleText,Select Destination,"
277: getPDFfiletags += "GETFILE_DoSaveMode,1,"
278: getPDFfiletags += "GA_RelVerify,1,"
279: getPDFfiletags += "GETFILE_ReadOnly,1,"
280: getPDFfiletags += "GETFILE_DoMultiSelect,0,"
281: getPDFfiletags += "GETFILE_Drawer,RAM:,"
282: getPDFfiletags += "GETFILE_File,poster.pdf,"
283: getPDFfiletags += "TAG_DONE"
284:
285:
286:
287: (rc,rc2,guikey) = arexx.dorexx("PROACTION","CREATEGUI PORTNAME " + pyport.name + " TAGSTRING "" + wintags + """)
288: if rc == 0:
289: # we can now build our GUI here;
290:
291: # firstly we add the top level layout gadget, we use a special command
292: # ADDLAYOUT to do this, which in combination with ENDLAYOUT manages nesting
293: # of complex layout trees for us.
294:
295: (rc,rc2,current_layout_gid) = arexx.dorexx("PROACTION","ADDLAYOUT GUIID " + guikey + " TAGSTRING "" + layouttags + """)
296:
297: # Here we add a label image directly to the layout with the ADDIMAGE command
298:
299:
300: (rc,rc2,dummy) = arexx.dorexx("PROACTION","ADDIMAGE GUIID " + guikey + " IMAGECLASS "label.image" TAGSTRING "" + labeltags + """)
301:
302: app.pagesizelist = GetPageSizeList()
303: numlabels = len(app.pagesizelist)
304: app.cnlist = MakeChooserNodeList(guikey,app.pagesizelist)
305:
306: if (app.cnlist != None):
307: (rc,rc2,app.cnlist_gid) = arexx.dorexx("PROACTION","ADDGADGET GUIID " + guikey + " GADGETCLASS "chooser.gadget" TAGSTRING "CHOOSER_Labels," + app.cnlist + ",CHOOSER_MaxLabels," + str(numlabels) + ",CHOOSER_Selected," + app.selected + ",GA_RelVerify,1,GA_Disabled,0,TAG_DONE"")
308:
309: # This next two lines demonstrates how to add a label to gadget.
310: # The label must be applied to the CHILD object of our current_layout
311: # Using the LAYOUT_ModifyChild tag.
312: # It's only safe to use this tag before the GUI window is opened.
313: # Otherwise we must use the LMMODIFYCHILD command
314:
315: # NB when we create an image this way we must set the NODISPOSE option.
316: # The reason being is that the lautout gad wil dispose of the attached image
317: # Automatically. So we don't want ProAction to do it. (Double dispose == death...)
318: # By default images create with NEWIMAGE are disposed of when the ProAction
319: # object is destroyed.
320:
321: (rc,rc2,label_iid) = arexx.dorexx("PROACTION","NEWIMAGE GUIID " + guikey + " IMAGECLASS "label.image" NODISPOSE TAGSTRING "LABEL_Justification,LJ_RIGHT,LABEL_Text,Page size:,TAG_DONE"")
322: (rc,rc2,dummy) = arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + current_layout_gid + " TAGSTRING "LAYOUT_ModifyChild," + app.cnlist_gid + ",CHILD_Label," + label_iid + ",TAG_DONE"")
323:
324:
325: (rc,rc2,app.getPSfile_gid) = arexx.dorexx("PROACTION","ADDGADGET GUIID " + guikey + " GADGETCLASS "getfile.gadget" TAGSTRING "" + getPSfiletags + """)
326: (rc,rc2,label_iid) = arexx.dorexx("PROACTION","NEWIMAGE GUIID " + guikey + " IMAGECLASS "label.image" NODISPOSE TAGSTRING "LABEL_Justification,LJ_RIGHT,LABEL_Text,Postscript file to convert:,TAG_DONE"")
327: (rc,rc2,dummy) = arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + current_layout_gid + " TAGSTRING "LAYOUT_ModifyChild," + app.getPSfile_gid + ",CHILD_Label," + label_iid + ",TAG_DONE"")
328:
329: (rc,rc2,app.getPDFfile_gid) = arexx.dorexx("PROACTION","ADDGADGET GUIID " + guikey + " GADGETCLASS "getfile.gadget" TAGSTRING "" + getPDFfiletags + """)
330: (rc,rc2,label_iid) = arexx.dorexx("PROACTION","NEWIMAGE GUIID " + guikey + " IMAGECLASS "label.image" NODISPOSE TAGSTRING "LABEL_Justification,LJ_RIGHT,LABEL_Text,PDF file to output:,TAG_DONE"")
331: (rc,rc2,dummy) = arexx.dorexx("PROACTION","SETATTRS GUIID " + guikey + " OBJECTID " + current_layout_gid + " TAGSTRING "LAYOUT_ModifyChild," + app.getPDFfile_gid + ",CHILD_Label," + label_iid + ",TAG_DONE"")
332:
333: # Now we add a button gadget. The ADDGADGET function adds the
334: # gadget to the currently active layout. We save the resulting ID
335: # in our Application class object app.
336:
337: (rc,rc2,app.convert_gid) = arexx.dorexx("PROACTION","ADDGADGET GUIID " + guikey + " GADGETCLASS "button.gadget" TAGSTRING "GA_Text,_Convert To PDF,GA_RelVerify,1,TAG_DONE"")
338:
339: # End of top level layout.
340: (rc,rc2,current_layout_gid) = arexx.dorexx("PROACTION","ENDLAYOUT GUIID " + guikey )
341:
342: # Open our Window.
343:
344: (rc,rc2,result) = arexx.dorexx("PROACTION","OPENGUIWINDOW GUIID " + guikey)
345:
346: # Handle events at the ARexx port.
347: HandleInput(pyport,guikey)
348:
349: # Now that we have a chhoser node we **Must** close the window before
350: # Releaseing the nodelist
351:
352: arexx.dorexx("PROACTION","CLOSEGUIWINDOW GUIID " + guikey)
353:
354: # Clean UP Our Nodes
355: FreeChooserNodeList(guikey, app.cnlist)
356:
357: # All done now shutdown the GUI
358: arexx.dorexx("PROACTION","DESTROYGUI GUIID " + guikey)
359: else: # pyport
360: ErrorExit("Couldn't create our ARexx port")
361:
362: #
363: # ErrorExit() throws up an RequestChoice notification to indicate
364: # a critical error.
365: #
366:
367: def ErrorExit(msg):
368: # Build our command as a string.
369: # We can use sys.argv[0] for the script name to make thsi more reusable.
370: command = "RequestChoice TYPE "ERROR" "" + sys.argv[0] + "" "" + msg + "" "OK" >NIL:"
371: # Call the command via the os.system()
372: os.system(command)
373: exit()
374:
375: #
376: # The support GetPorts() returns a list of public MsgPorts
377: # as a string
378: #
379:
380:
381: def GetPorts():
382: # Depending on the version of python we may have a useful
383: # function that retuns just what we need, so we use the try
384: # except syntax to test.
385:
386: try:
387: ports = os.getports()
388: ports = " ".join(ports)
389: except AttributeError:
390: # The getports function wasn't present so we call the REXX
391: # equivalent.
392: (rc,rc2,ports) = arexx.dorexx("REXX","return show('P')")
393: if rc != 0:
394: # We couldn't even call REXX somwthing serioiusly up here
395: # Bail out with a requester.
396: ErrorExit("Couldn't Find ARexx!")
397: return ports
398:
399:
400: # Now we need to check that ProAction is present an available to us,
401: # So first get a list of active ports
402:
403: ports = GetPorts()
404:
405: # Then check for ProActions port which will be "PROACTION".
406: # Port names are case sensitive.
407:
408: if -1 == ports.find("PROACTION"):
409: # No ProAction start it
410: os.system("RUN >"T:proactionpid" *>NIL: APPDIR:PROACTION")
411: os.system("C:WaitForPort PROACTION")
412: # Now check again
413: ports = GetPorts()
414: if -1 == ports.find("PROACTION"):
415: # Still not there :-(
416: ErrorExit("Unable to start or find ProAction GUIServer")
417:
418:
419: # Okay we should be good to go now
420: # Initialise our aplication
421:
422: app = Application();
423:
424: DoGUI("Workbench")
425: