1
2
3 __version__ = '$Revision: 4791 $'.split()[1]
4 __date__ = '$Date: 2006-09-24 14:01:41 -0400 (Sun, 24 Sep 2006) $'.split()[1]
5 __author__ = 'Kurt Schwehr'
6
7 __doc__='''
8
9 Build a wxpython interface that uses the aisxmlbinmsg2py generated file to create a message string.
10
11 aisxmlbinmsg2py was getting too long, so this functionality is completely broken out.
12
13 @requires: U{lxml<http://codespeak.net/lxml/>}
14 @requires: U{epydoc<http://epydoc.sourceforge.net/>} >= 3.0alpha3
15 @requires: U{BitVector<http://cheeseshop.python.org/pypi/BitVector>} >= 1.2
16
17 @author: U{'''+__author__+'''<http://schwehr.org/>}
18 @version: ''' + __version__ +'''
19 @copyright: 2006
20 @var __date__: Date of last svn commit
21 @undocumented: __version__ __author__ __doc__ parser
22 @since: 2006-Sep-24
23 @status: under development
24 @organization: U{CCOM<http://ccom.unh.edu/>}
25 @license: GPL v2
26
27 @bug: FIX: Handle text fields
28 @bug: FIX: deal with the name mangling flag in the xml?
29 '''
30
31
32 import sys, os
33 from lxml import etree
34
36 '''
37 @return: true if the tag a sub tag with name subtag
38 '''
39 if 0<len(et.xpath(subtag)): return True
40 return False
41
43 '''
44 @return: true there exists a bool type field
45 @param et: message element tree
46 '''
47 if 0<len(et.xpath('field[@type="bool"]')): return True
48 return False
49
50
52 '''
53 @return: true if should use a wxChoice for this field
54 @param field: Field element tree
55 '''
56 fieldType = field.attrib['type']
57 if fieldType not in ['int','uint','bool']:
58 return False
59
60 if fieldType == 'bool': return True
61 if int(field.attrib['numberofbits'])>4: return False
62 if hasSubTag(field,'lookuptable'): return True
63 return False
64
66 '''Create the wx.Choice list of entries'''
67 lookup = fieldET.xpath('lookuptable')[0]
68 name = fieldET.attrib['name']
69 fieldType = fieldET.attrib['type']
70 if fieldType == 'int':
71 assert False
72 elif fieldType == 'uint':
73 o.write('\t'+name+'List = [\n')
74 lastVal=0
75 for entry in lookup.xpath('entry'):
76 entryKey = int(entry.attrib['key'])
77
78 for i in range(lastVal,entryKey):
79 o.write('\t\t\''+str(i)+'\',\n')
80 lastVal = entryKey + 1
81 o.write('\t\t\''+str(entryKey)+' - '+entry.text+'\',\n')
82 o.write('\t\t]\n')
83 elif fieldType == 'bool':
84 pass
85 else:
86 print 'ERROR: not handling the choice for ',name,fieldType
87
88
89
91 '''
92 @param infile: xml ais binary message definition file
93 @param outfile: where to dump the python code
94 '''
95
96 aisMsgsET = etree.parse(infile).getroot()
97
98 o = file(outfile,'w')
99 os.chmod(outfile,0755)
100
101 print 'FIX: make the python command user selectable'
102 o.write('''#!/usr/bin/env pythonw # mac specific
103 # FIX: put some documentation here!
104
105 import wx
106 ''')
107
108
109
110 print 'FIX: make a more rebust determination of the module name'
111 o.write('#import '+outfile.split('.')[0]+'\t# FIX: turn this back on\n')
112
113
114 o.write('''
115
116 testParams = {'COG': 34.5,
117 'MessageID': 1,
118 'NavigationStatus': 3,
119 'PositionAccuracy': 1,
120 'Position_latitude': 37.424458333333334,
121 'Position_longitude': -122.16328055555556,
122 'RAIM': False,
123 'ROT': -2,
124 'RegionalReserved': 0,
125 'RepeatIndicator': 1,
126 'SOG': 101.9,
127 'Spare': 0,
128 'TimeStamp': 35,
129 'TrueHeading': 41,
130 'UserID': 1193046,
131 'slotoffset': 1221,
132 'syncstate': 2}
133
134 ''')
135
136 for msgET in aisMsgsET.xpath('message'):
137
138 print msgET.tag, msgET.attrib['name']
139
140 if len(msgET.xpath('include-struct')) > 0:
141 sys.exit("ERROR: cannot handle xml that still has include-struct tags.\n Please use expandais.py.")
142 buildWxPythonMsg(o,msgET,prefixName=prefixName,verbose=verbose)
143
144
146 '''
147 Write a class for the wx python.
148
149 @param o: open file where resulting code will be written
150 @param msgET: Element Tree starting at a message node
151
152 @todo: for lookuptable/entry values, make it also print the decoded value.
153 @todo: use a different name for message and field
154 '''
155 assert(msgET.tag=='message')
156 msgName = msgET.attrib['name']
157
158
159 className = 'MsgFrame'
160 if prefixName: className = msgName+'MsgFrame'
161
162
163 o.write('class '+className+'(wx.Frame):\n')
164 o.write('''\t\'\'\'
165 # FIX: write doc string here for the frame
166 \'\'\'
167
168 ''')
169 for field in msgET.xpath('field'):
170 if verbose: print 'Does field',field.attrib['name'],'use choice ...',
171 if useChoice(field):
172 createChoiceList(o,field)
173 if verbose: print 'yes'
174 elif verbose: print 'no'
175
176
177 if hasBoolField(msgET): o.write('\tBoolList=[\'False\',\'True\']\n')
178
179
180 o.write('''
181 def __init__(self,parent,title,msgDict):
182 \'\'\'
183 @param msgDict: Default values to use.
184 Overwritten with the return values.
185 Values that are required will be ignored.
186 @type msgDict: dict
187 \'\'\'
188 wx.Frame.__init__(self,parent,-1,title,size=(640,480)) # FIX: what size?
189 self.msgDict = msgDict # Save this so we can edit and return valies in the incoming dict
190
191 defaults = testParams # FIX: turn off this hack and use the unavailable values
192
193 sizer=wx.FlexGridSizer(rows=1,cols=2, vgap=13, hgap=13)
194 sizer.AddGrowableCol(1)
195 self.SetSizer(sizer)
196
197 ''')
198
199 for field in msgET.xpath('field'):
200
201 name = field.attrib['name']
202 fieldType = field.attrib['type']
203 numBits = int(field.attrib['numberofbits'])
204
205 o.write('\n\t\t############################ Field '+name+' - '+fieldType+'\n')
206 o.write('\t\ttxt = wx.StaticText(self,label=\''+name+'\')\n')
207 if hasSubTag(field,'required'):
208 o.write('\t\tself.'+name+'Widget=wx.StaticText(self,label=\''+field.xpath('required')[0].text+'\')\n')
209 else:
210 o.write('\t\tvalue=str(defaults[\''+name+'\'])\n')
211 o.write('\t\tif \''+name+'\' in msgDict: value = str(msgDict[\''+name+'\'])\n')
212 o.write('\t\tself.'+name+'Widget=')
213 if 'uint'==fieldType:
214 if useChoice(field):
215 o.write('wx.Choice(self,-1, choices = self.'+name+'List)\n')
216 o.write('\t\tself.'+name+'Widget.SetSelection(int(value))\n')
217 else: o.write('wx.SpinCtrl(self,value=value,min=0,max='+str(2**numBits - 1)+')\n')
218 elif 'int'==fieldType:
219 if useChoice(field):
220 o.write('wx.Choice(self,-1, choices = self.'+name+'List)\n')
221
222 assert False
223 else: o.write('wx.SpinCtrl(self,value=value,min='+str(2**(numBits-1))+',max='+str(2**(numBits-1) - 1)+')\n')
224 elif 'bool'==fieldType:
225 o.write('wx.Choice(self,-1, choices = self.BoolList)\n')
226 o.write('\t\tif defaults[\''+name+'\']: self.'+name+'Widget.SetSelection(1)\n')
227 o.write('\t\telse: self.'+name+'Widget.SetSelection(0)\n')
228 elif fieldType in ['udecimal','decimal']:
229 scale = float(field.xpath('scale')[0].text)
230 if fieldType=='udecimal':
231 o.write('wx.Slider(self,-1,float(value),0,'+str((2**numBits -1) / scale))
232 else:
233
234
235 start = '-'+str((2**(numBits-1)) / scale)
236 end = str((2**(numBits-1) - 1) / scale)
237 if hasSubTag(field,'range'):
238
239 range = field.xpath('range')[0]
240 start = float(range.attrib['min'])
241 end = float(range.attrib['max'])
242 if hasSubTag(field,'unavailable'):
243 unavailable = float(field.xpath('unavailable')[0].text)
244 if unavailable < start: start = unavailable
245 if end < unavailable: end = unavailable
246
247
248 o.write('wx.Slider(self,-1,float(value),'+str(start)+','+str(end))
249 o.write(',style=wx.SL_HORIZONTAL|wx.SL_AUTOTICKS | wx.SL_LABELS)\n')
250
251
252 else:
253 print 'Please follow the GPS navigation system over the cliff',name,fieldType
254 assert(False)
255
256 o.write('\t\tdel value\n')
257
258 o.write('\n')
259 o.write('\t\tsizer.Add(item=txt); del txt\n')
260 o.write('\t\tsizer.Add(self.'+name+'Widget,flag=wx.EXPAND)\n')
261
262 o.write('''
263 #######
264 ####### FINISH UP __init__
265 #######
266 btnDone = wx.Button(self,label='Done')
267
268 sizer.Add((1,35),0,wx.ADJUST_MINSIZE,0)
269 sizer.Add(item=btnDone)
270 btnDone.Bind(wx.EVT_BUTTON,self.OnDone)
271
272 self.Layout() # Get yourself organized
273 self.Show()
274
275 def OnDone(self,evt):
276 \'\'\'
277 Put all values into msgDict so that they get returned to the caller
278 \'\'\'
279 ''')
280 for field in msgET.xpath('field'):
281 name = field.attrib['name']
282 fieldType = field.attrib['type']
283
284 if hasSubTag(field,'required'):
285
286 if fieldType in ['int','uint']:
287 o.write('\t\tself.msgDict[\''+name+'\']='+field.xpath('required')[0].text+'\n')
288 else:
289 print 'FIX: need to write this case!'
290 assert False
291 else:
292 if fieldType in ['int','uint','bool','udecimal','decimal']:
293 if useChoice(field): o.write('\t\tself.msgDict[\''+name+'\']=self.'+name+'Widget.GetSelection()\n')
294 else: o.write('\t\tself.msgDict[\''+name+'\']=self.'+name+'Widget.GetValue()\n')
295 elif fieldType in ['decimal','udecimal']:
296 print 'FIX: what do I do about decimals?'
297 o.write('\t\t#FIX: handle '+name+' '+fieldType+'\n')
298 else:
299 print 'FIX: need to write the other cases here', name, fieldType
300 o.write('\t\t#FIX: handle '+name+' '+fieldType+'\n')
301 assert False
302
303 o.write('\t\tself.Close(True)\n')
304
305
306
307
308
309 o.write('''
310 if __name__=='__main__':
311 app = wx.PySimpleApp()
312 theData={}
313 frame = MsgFrame(None,\''''+msgName.capitalize()+''' Message Editor',theData)
314 app.MainLoop()
315
316 print 'finishing up', theData
317 ''')
318
319
320
321 if __name__=='__main__':
322 from optparse import OptionParser
323 parser = OptionParser(usage="%prog [options]",
324 version="%prog "+__version__)
325
326 parser.add_option('-o','--output',dest='outputFileName',default=None,
327 help='Name of the python file to write')
328
329
330 parser.add_option('-i','-x','--xml-definition',dest='xmlFileName',default=None,
331 help='XML definition file for the msg to use')
332
333 parser.add_option('--doc-test',dest='doctest',default=False,action='store_true',
334 help='run the documentation tests')
335
336 parser.add_option('-p','--prefix',dest='prefix',default=False,action='store_true',
337 help='put the field name in front of all function names.'
338 +' Allows multiple messages in one file')
339
340 parser.add_option('-v','--verbose',dest='verbose',default=False,action='store_true',
341 help='run the tests run in verbose mode')
342
343 (options,args) = parser.parse_args()
344
345 success=True
346
347 if options.doctest:
348 import os; print os.path.basename(sys.argv[0]), 'doctests ...',
349 argvOrig = sys.argv
350 sys.argv= [sys.argv[0]]
351 if options.verbose: sys.argv.append('-v')
352 import doctest
353 numfail,numtests=doctest.testmod()
354 if numfail==0: print 'ok'
355 else:
356 print 'FAILED'
357 success=False
358 sys.argv = argvOrig
359 del argvOrig
360 sys.exit()
361
362 if None==options.xmlFileName:
363 sys.exit('ERROR: must specify an xml definition file.')
364 if None==options.outputFileName:
365 sys.exit('ERROR: must specify an python file to write to.')
366 generateWxPython(options.xmlFileName,options.outputFileName,prefixName=options.prefix
367 ,verbose=options.verbose)
368
369 print '\nrecommend running pychecker like this:'
370 print ' pychecker -q',options.outputFileName
371