flp.py revision 9694fcab5332f27dc28b195ba1391e5491d2eaef
1#
2# flp - Module to load fl forms from fd files
3#
4# Jack Jansen, December 1991
5#
6import string
7import os
8import sys
9import FL
10
11SPLITLINE = '--------------------'
12FORMLINE = '=============== FORM ==============='
13ENDLINE = '=============================='
14
15error = 'flp.error'
16
17##################################################################
18#    Part 1 - The parsing routines                               #
19##################################################################
20
21#
22# Externally visible function. Load form.
23#
24def parse_form(filename, formname):
25    forms = checkcache(filename)
26    if forms is None:
27	forms = parse_forms(filename)
28    if forms.has_key(formname):
29	return forms[formname]
30    else:
31	raise error, 'No such form in fd file'
32
33#
34# Externally visible function. Load all forms.
35#
36def parse_forms(filename):
37    forms = checkcache(filename)
38    if forms != None: return forms
39    fp = _open_formfile(filename)
40    nforms = _parse_fd_header(fp)
41    forms = {}
42    for i in range(nforms):
43	form = _parse_fd_form(fp, None)
44	forms[form[0].Name] = form
45    writecache(filename, forms)
46    return forms
47
48#
49# Internal: see if a cached version of the file exists
50#
51MAGIC = '.fdc'
52_internal_cache = {}			# Used by frozen scripts only
53def checkcache(filename):
54    if _internal_cache.has_key(filename):
55	altforms = _internal_cache[filename]
56	return _unpack_cache(altforms)
57    import marshal
58    fp, filename = _open_formfile2(filename)
59    fp.close()
60    cachename = filename + 'c'
61    try:
62	fp = open(cachename, 'r')
63    except IOError:
64	#print 'flp: no cache file', cachename
65	return None
66    try:
67	if fp.read(4) != MAGIC:
68	    print 'flp: bad magic word in cache file', cachename
69	    return None
70	cache_mtime = rdlong(fp)
71	file_mtime = getmtime(filename)
72	if cache_mtime != file_mtime:
73	    #print 'flp: outdated cache file', cachename
74	    return None
75	#print 'flp: valid cache file', cachename
76	altforms = marshal.load(fp)
77	return _unpack_cache(altforms)
78    finally:
79	fp.close()
80
81def _unpack_cache(altforms):
82	forms = {}
83	for name in altforms.keys():
84	    altobj, altlist = altforms[name]
85	    obj = _newobj()
86	    obj.make(altobj)
87	    list = []
88	    for altobj in altlist:
89		nobj = _newobj()
90		nobj.make(altobj)
91		list.append(nobj)
92	    forms[name] = obj, list
93	return forms
94
95def rdlong(fp):
96    s = fp.read(4)
97    if len(s) != 4: return None
98    a, b, c, d = s[0], s[1], s[2], s[3]
99    return ord(a)<<24 | ord(b)<<16 | ord(c)<<8 | ord(d)
100
101def wrlong(fp, x):
102    a, b, c, d = (x>>24)&0xff, (x>>16)&0xff, (x>>8)&0xff, x&0xff
103    fp.write(chr(a) + chr(b) + chr(c) + chr(d))
104
105def getmtime(filename):
106    import os
107    from stat import ST_MTIME
108    try:
109	return os.stat(filename)[ST_MTIME]
110    except os.error:
111	return None
112
113#
114# Internal: write cached version of the form (parsing is too slow!)
115#
116def writecache(filename, forms):
117    import marshal
118    fp, filename = _open_formfile2(filename)
119    fp.close()
120    cachename = filename + 'c'
121    try:
122	fp = open(cachename, 'w')
123    except IOError:
124	print 'flp: can\'t create cache file', cachename
125	return # Never mind
126    fp.write('\0\0\0\0') # Seek back and write MAGIC when done
127    wrlong(fp, getmtime(filename))
128    altforms = _pack_cache(forms)
129    marshal.dump(altforms, fp)
130    fp.seek(0)
131    fp.write(MAGIC)
132    fp.close()
133    #print 'flp: wrote cache file', cachename
134
135#
136# External: print some statements that set up the internal cache.
137# This is for use with the "freeze" script.  You should call
138# flp.freeze(filename) for all forms used by the script, and collect
139# the output on a file in a module file named "frozenforms.py".  Then
140# in the main program of the script import frozenforms.
141# (Don't forget to take this out when using the unfrozen version of
142# the script!)
143#
144def freeze(filename):
145    forms = parse_forms(filename)
146    altforms = _pack_cache(forms)
147    print 'import flp'
148    print 'flp._internal_cache[', `filename`, '] =', altforms
149
150#
151# Internal: create the data structure to be placed in the cache
152#
153def _pack_cache(forms):
154    altforms = {}
155    for name in forms.keys():
156	obj, list = forms[name]
157	altobj = obj.__dict__
158	altlist = []
159	for obj in list: altlist.append(obj.__dict__)
160	altforms[name] = altobj, altlist
161    return altforms
162
163#
164# Internal: Locate form file (using PYTHONPATH) and open file
165#
166def _open_formfile(filename):
167    return _open_formfile2(filename)[0]
168
169def _open_formfile2(filename):
170    if filename[-3:] <> '.fd':
171	filename = filename + '.fd'
172    if filename[0] == '/':
173	try:
174	    fp = open(filename,'r')
175	except IOError:
176	    fp = None
177    else:
178	for pc in sys.path:
179	    pn = os.path.join(pc, filename)
180	    try:
181		fp = open(pn, 'r')
182		filename = pn
183		break
184	    except IOError:
185		fp = None
186    if fp == None:
187	raise error, 'Cannot find forms file ' + filename
188    return fp, filename
189
190#
191# Internal: parse the fd file header, return number of forms
192#
193def _parse_fd_header(file):
194    # First read the magic header line
195    datum = _parse_1_line(file)
196    if datum <> ('Magic', 12321):
197	raise error, 'Not a forms definition file'
198    # Now skip until we know number of forms
199    while 1:
200	datum = _parse_1_line(file)
201	if type(datum) == type(()) and datum[0] == 'Numberofforms':
202	    break
203    return datum[1]
204#
205# Internal: parse fd form, or skip if name doesn't match.
206# the special value None means 'allways parse it'.
207#
208def _parse_fd_form(file, name):
209    datum = _parse_1_line(file)
210    if datum <> FORMLINE:
211	raise error, 'Missing === FORM === line'
212    form = _parse_object(file)
213    if form.Name == name or name == None:
214	objs = []
215	for j in range(form.Numberofobjects):
216	    obj = _parse_object(file)
217	    objs.append(obj)
218	return (form, objs)
219    else:
220	for j in range(form.Numberofobjects):
221	    _skip_object(file)
222    return None
223
224#
225# Internal class: a convient place to store object info fields
226#
227class _newobj:
228    def add(self, name, value):
229	self.__dict__[name] = value
230    def make(self, dict):
231	for name in dict.keys():
232	    self.add(name, dict[name])
233
234#
235# Internal parsing routines.
236#
237def _parse_string(str):
238    if '\\' in str:
239	s = '\'' + str + '\''
240	try:
241	    return eval(s)
242	except:
243	    pass
244    return str
245
246def _parse_num(str):
247    return eval(str)
248
249def _parse_numlist(str):
250    slist = string.split(str)
251    nlist = []
252    for i in slist:
253	nlist.append(_parse_num(i))
254    return nlist
255
256# This dictionary maps item names to parsing routines.
257# If no routine is given '_parse_num' is default.
258_parse_func = { \
259	'Name':		_parse_string, \
260	'Box':		_parse_numlist, \
261	'Colors':	_parse_numlist, \
262	'Label':	_parse_string, \
263	'Name':		_parse_string, \
264	'Callback':	_parse_string, \
265	'Argument':	_parse_string }
266
267# This function parses a line, and returns either
268# a string or a tuple (name,value)
269
270import re
271prog = re.compile('^([^:]*): *(.*)')
272
273def _parse_line(line):
274    match = prog.match(line)
275    if not match:
276	return line
277    name, value = match.group(1, 2)
278    if name[0] == 'N':
279	    name = string.join(string.split(name),'')
280	    name = string.lower(name)
281    name = string.capitalize(name)
282    try:
283	pf = _parse_func[name]
284    except KeyError:
285	pf = _parse_num
286    value = pf(value)
287    return (name, value)
288
289def _readline(file):
290    line = file.readline()
291    if not line:
292    	raise EOFError
293    return line[:-1]
294
295def _parse_1_line(file):
296    line = _readline(file)
297    while line == '':
298	line = _readline(file)
299    return _parse_line(line)
300
301def _skip_object(file):
302    line = ''
303    while not line in (SPLITLINE, FORMLINE, ENDLINE):
304	pos = file.tell()
305	line = _readline(file)
306    if line == FORMLINE:
307	file.seek(pos)
308
309def _parse_object(file):
310    obj = _newobj()
311    while 1:
312	pos = file.tell()
313	datum = _parse_1_line(file)
314	if datum in (SPLITLINE, FORMLINE, ENDLINE):
315	    if datum == FORMLINE:
316		file.seek(pos)
317	    return obj
318	if type(datum) <> type(()) or len(datum) <> 2:
319	    raise error, 'Parse error, illegal line in object: '+datum
320	obj.add(datum[0], datum[1])
321
322#################################################################
323#   Part 2 - High-level object/form creation routines            #
324#################################################################
325
326#
327# External - Create a form an link to an instance variable.
328#
329def create_full_form(inst, (fdata, odatalist)):
330    form = create_form(fdata)
331    exec 'inst.'+fdata.Name+' = form\n'
332    for odata in odatalist:
333	create_object_instance(inst, form, odata)
334
335#
336# External - Merge a form into an existing form in an instance
337# variable.
338#
339def merge_full_form(inst, form, (fdata, odatalist)):
340    exec 'inst.'+fdata.Name+' = form\n'
341    if odatalist[0].Class <> FL.BOX:
342	raise error, 'merge_full_form() expects FL.BOX as first obj'
343    for odata in odatalist[1:]:
344	create_object_instance(inst, form, odata)
345
346
347#################################################################
348#   Part 3 - Low-level object/form creation routines            #
349#################################################################
350
351#
352# External Create_form - Create form from parameters
353#
354def create_form(fdata):
355    import fl
356    return fl.make_form(FL.NO_BOX, fdata.Width, fdata.Height)
357
358#
359# External create_object - Create an object. Make sure there are
360# no callbacks. Returns the object created.
361#
362def create_object(form, odata):
363    obj = _create_object(form, odata)
364    if odata.Callback:
365	raise error, 'Creating free object with callback'
366    return obj
367#
368# External create_object_instance - Create object in an instance.
369#
370def create_object_instance(inst, form, odata):
371    obj = _create_object(form, odata)
372    if odata.Callback:
373	cbfunc = eval('inst.'+odata.Callback)
374	obj.set_call_back(cbfunc, odata.Argument)
375    if odata.Name:
376	exec 'inst.' + odata.Name + ' = obj\n'
377#
378# Internal _create_object: Create the object and fill options
379#
380def _create_object(form, odata):
381    crfunc = _select_crfunc(form, odata.Class)
382    obj = crfunc(odata.Type, odata.Box[0], odata.Box[1], odata.Box[2], \
383	    odata.Box[3], odata.Label)
384    if not odata.Class in (FL.BEGIN_GROUP, FL.END_GROUP):
385	obj.boxtype = odata.Boxtype
386	obj.col1 = odata.Colors[0]
387	obj.col2 = odata.Colors[1]
388	obj.align = odata.Alignment
389	obj.lstyle = odata.Style
390	obj.lsize = odata.Size
391	obj.lcol = odata.Lcol
392    return obj
393#
394# Internal crfunc: helper function that returns correct create function
395#
396def _select_crfunc(fm, cl):
397    if cl == FL.BEGIN_GROUP: return fm.bgn_group
398    elif cl == FL.END_GROUP: return fm.end_group
399    elif cl == FL.BITMAP: return fm.add_bitmap
400    elif cl == FL.BOX: return fm.add_box
401    elif cl == FL.BROWSER: return fm.add_browser
402    elif cl == FL.BUTTON: return fm.add_button
403    elif cl == FL.CHART: return fm.add_chart
404    elif cl == FL.CHOICE: return fm.add_choice
405    elif cl == FL.CLOCK: return fm.add_clock
406    elif cl == FL.COUNTER: return fm.add_counter
407    elif cl == FL.DIAL: return fm.add_dial
408    elif cl == FL.FREE: return fm.add_free
409    elif cl == FL.INPUT: return fm.add_input
410    elif cl == FL.LIGHTBUTTON: return fm.add_lightbutton
411    elif cl == FL.MENU: return fm.add_menu
412    elif cl == FL.POSITIONER: return fm.add_positioner
413    elif cl == FL.ROUNDBUTTON: return fm.add_roundbutton
414    elif cl == FL.SLIDER: return fm.add_slider
415    elif cl == FL.VALSLIDER: return fm.add_valslider
416    elif cl == FL.TEXT: return fm.add_text
417    elif cl == FL.TIMER: return fm.add_timer
418    else:
419	raise error, 'Unknown object type: ' + `cl`
420
421
422def test():
423    import time
424    t0 = time.time()
425    if len(sys.argv) == 2:
426	forms = parse_forms(sys.argv[1])
427	t1 = time.time()
428	print 'parse time:', 0.001*(t1-t0), 'sec.'
429	keys = forms.keys()
430	keys.sort()
431	for i in keys:
432	    _printform(forms[i])
433    elif len(sys.argv) == 3:
434	form = parse_form(sys.argv[1], sys.argv[2])
435	t1 = time.time()
436	print 'parse time:', round(t1-t0, 3), 'sec.'
437	_printform(form)
438    else:
439	print 'Usage: test fdfile [form]'
440
441def _printform(form):
442    f = form[0]
443    objs = form[1]
444    print 'Form ', f.Name, ', size: ', f.Width, f.Height, ' Nobj ', f.Numberofobjects
445    for i in objs:
446	print '  Obj ', i.Name, ' type ', i.Class, i.Type
447	print '    Box ', i.Box, ' btype ', i.Boxtype
448	print '    Label ', i.Label, ' size/style/col/align ', i.Size,i.Style, i.Lcol, i.Alignment
449	print '    cols ', i.Colors
450	print '    cback ', i.Callback, i.Argument
451
452# Local variables:
453# py-indent-offset: 4
454# end:
455