/[james]/pythonwimp/wimp.py
ViewVC logotype

Annotation of /pythonwimp/wimp.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 74 - (hide annotations) (download) (as text)
Sun May 15 12:08:06 2005 UTC (19 years, 6 months ago) by james
File MIME type: text/x-python
File size: 43360 byte(s)
Implement paragraph class using rufl module.

1 james 17 """Interface with RISC OS window manager (wimp) and related functions.
2    
3     Class hierarchy:
4    
5     window -- base window class
6     iconbar_icon -- iconbar icon
7     info_window -- info box
8     save_window -- save box
9     menu -- menu class
10     content -- window contents class
11     rectangle -- example content
12     font -- font class
13    
14     Functions:
15    
16     task() -- initialise the task with the wimp
17     quit() -- tidy up and exit
18    
19     poll() -- get a wimp event
20     handle() -- handle a wimp event
21    
22     screen_size() -- return screen mode dimensions
23     mem_string() -- read or write a string to a buffer
24    
25     hourglass_on() -- switch on the hourglass
26     hourglass_off() -- switch off the hourglass
27     hourglass_percentage() -- display a percentage underneath
28    
29     warning() -- display a wimp error box
30     lookup() -- convert a message token to a message
31    
32     send_message() -- send a wimp message
33     close_menus() -- close any open menus
34     set_colour() -- set the colour for plotting
35     plot() -- plot to the screen
36    
37     Variables:
38    
39     task_handle -- wimp task handle
40     task_name -- wimp task name
41     poll_mask -- default wimp poll event mask
42    
43     Comments:
44    
45     All window coordinates behave as follows:
46     - the origin is assumed to be in the top left, so ensure that
47     your templates have this to avoid problems
48     - x coordinates are 0 at the origin and increase across the window
49     - y coordinates are 0 at the origin and increase down the window
50     - scroll offsets are also always positive
51    
52     Coordinates are usually in os units. However, the document classes
53     and fonts use millipoints. At 100%, 400 millipoints = 1 os unit.
54     Font sizes are in 1/16 points.
55    
56     """
57    
58     import os
59     import swi
60     import string
61     import sys
62     import types
63 james 74 import rufl
64 james 17
65     task_handle = 0
66     task_name = 'Application'
67     poll_mask = 1
68     null_reason_handler = None
69     user_drag_handler = None
70     message_handler = {}
71     windows = {}
72     fonts = {}
73    
74     _depth = 0
75    
76     def task(name, dir, task, messages = (), debug = 0, profile = 0, mask = None):
77     "Initialise the task with the wimp and load the messages file"
78     global task_name, directory, b, wimp_version, task_handle, territory, m, task_module, _trace
79 james 74
80 james 17 hourglass_on()
81     task_name = name
82     directory = dir
83     task_module = task
84     if mask:
85     poll_mask = mask
86    
87     # set up debugging stuff
88     if debug:
89     import traceback
90     sys.stderr = sys.stdout = open(directory + '.^.Output', 'w')
91     if debug == 2:
92     def _trace(frame, event, arg):
93     global _depth
94     if event == 'call':
95     print _depth * ' ', frame.f_code.co_name + '(', arg, ')'
96     _depth = _depth + 1
97     elif event == 'return':
98     print _depth * ' ', 'return', arg
99     _depth = _depth - 1
100     return _trace
101     sys.settrace(_trace)
102     elif debug == 3:
103     def _trace(frame, event, arg):
104     global _depth
105     if event == 'line':
106     print _depth * ' ', frame.f_lineno
107     elif event == 'call':
108     print _depth * ' ', frame.f_code.co_name + '(', arg, ')'
109     _depth = _depth + 1
110     elif event == 'return':
111     print _depth * ' ', 'return', arg
112     _depth = _depth - 1
113     else:
114     print _depth * ' ', event + ':', arg
115     return _trace
116     sys.settrace(_trace)
117 james 74
118 james 17 # buffer for various stuff
119     b = swi.block(64)
120 james 74
121 james 17 # initialise with wimp
122     # place the message list in the buffer
123     b[0] = 0x502 # Message_HelpRequest
124     b[1] = 0x400c9 # Message_MenusDeleted
125     index = 2
126     for message in messages:
127     b[index] = message
128     index = index + 1
129     b[index] = 0
130     # Wimp_Initialise
131     wimp_version, task_handle = swi.swi(0x400c0, 'iisb;ii', 310, 0x4b534154, name, b)
132    
133     # load messages
134     # Territory_NumberToName, Territory_Number
135     territory = swi.swi(0x43043, 'ibi;.s', swi.swi(0x43040, ';i'), b, 256)
136    
137     try:
138     message_file = open(directory + '.Resources.' + territory + '.Messages', 'r')
139     except IOError:
140     message_file = open(directory + '.Resources.UK.Messages', 'r')
141     m = {}
142     for line in message_file.readlines():
143     if line[0] != '#' and ':' in line:
144     message = string.split(line, ':', 1)
145     m[message[0]] = message[1][:-1]
146     message_file.close()
147    
148     try:
149     if profile:
150     import profile
151     profile.run('wimp._go()', directory + '.^.Profile')
152     else:
153     _go()
154     except SystemExit:
155     hourglass_on()
156     task_module.quit()
157     except RuntimeError, value:
158     swi.swi(0x400df, 'sis', ' ' + str(value), 1, task_name)
159     except:
160     if debug > 1:
161     sys.settrace(None)
162 james 74
163 james 17 type, value, traceb = sys.exc_info()
164     swi.swi(0x400df, 'sis',
165     ' Internal error - must exit (' + str(type) + ': ' + str(value) + ')',
166     1, task_name)
167 james 74
168 james 17 if debug:
169     traceback.print_exc()
170     trace = traceback.extract_tb(traceb)
171     try:
172     swi.swi(0x42587, '')
173     swi.swi(0x42588, '0.s', trace[-1][0])
174     swi.swi(0x42588, '1.si1s', trace[-1][0], trace[-1][1],
175     str(type) + ': ' + trace[-1][2] + ': ' + trace[-1][3])
176     swi.swi(0x42589, '')
177     except:
178     pass
179 james 74
180 james 17 if sys.stderr.tell() > 0:
181     sys.stderr.close()
182     os.system('Filer_Run ' + directory + '.^.Output')
183     else:
184     sys.stderr.close()
185 james 74
186 james 17 # Wimp_CloseDown
187     swi.swi(0x400dd, 'ii', task_handle, 0x4b534154)
188     hourglass_off()
189    
190 james 74
191 james 17 def _go():
192     "Run the program"
193     task_module.init()
194     hourglass_off()
195     while 1:
196     handle(poll(poll_mask))
197 james 74
198    
199 james 17 def quit():
200     "Trigger quitting"
201     for f in fonts.values():
202     f.use = 1
203     f.lose()
204     raise SystemExit
205    
206    
207     def poll(mask = None):
208     "Poll wimp once and return data"
209     # Wimp_Poll
210     if mask is None:
211     mask = poll_mask
212     event = swi.swi(0x400c7, 'ib;i', mask, b)
213     if event == 2: # Open_Window_Request
214     return 2, (windows[b[0]], b[1], b[2], b[3], b[4], b[5], b[6], b[7])
215     elif event == 3: # Close_Window_Request
216     return 3, windows[b[0]]
217     elif event == 6: # Mouse_Click
218     x, y, z, w, i = b[0], b[1], b[2], b[3], b[4]
219     b[0] = w
220     # Wimp_GetWindowState
221     swi.swi(0x400cb, '.b', b)
222     return 6, (windows[w], i, z, x - (b[1] - b[5]), (b[4] - b[6]) - y, x, y)
223     elif event == 7: # User_Drag_Box
224     return 7, (b[0], b[1], b[2], b[3])
225     elif event == 8: # Key_Pressed
226     return 8, (windows[b[0]], b[1], b[6])
227     # User_Message, User_Message_Recorded, User_Message_Acknowledge
228     elif event == 17 or event == 18 or event == 19:
229     return event, (b[4])
230     return event, b
231    
232    
233     def handle((event, details)):
234     "Handle events as returned by poll()"
235     # try:
236     if event == 0: # Null_Reason_Code
237     if null_reason_handler:
238     null_reason_handler()
239     elif event == 1: # Redraw_Window_Request
240     windows[b[0]].do_redraw()
241     elif event == 2: # Open_Window_Request
242     details[0].open(details[1], details[2], details[3], details[4], details[5], details[6], details[7])
243     elif event == 3: # Close_Window_Request
244     details.close()
245     elif event == 4: # Pointer_Leaving_Window
246     if windows.has_key(b[0]):
247     windows[b[0]].leaving()
248     elif event == 5: # Pointer_Entering_Window
249     windows[b[0]].entering()
250     elif event == 6: # Mouse_Click
251     details[0].click(details[1], details[2], details[3], details[4], details[5], details[6])
252     elif event == 7: # User_Drag_Box
253     if user_drag_handler:
254     user_drag_handler(details[0], details[1], details[2], details[3])
255     elif event == 8: # Key_Pressed
256     details[0].key(details[1], details[2])
257     # User_Message, User_Message_Recorded, User_Message_Acknowledge
258     elif event == 17 or event == 18 or event == 19:
259     if details == 0: # Message_Quit
260     quit()
261     elif details == 0x502: # Message_HelpRequest
262     _help()
263     elif message_handler.has_key(details):
264     message_handler[details]()
265     # except KeyError:
266     # pass
267    
268    
269     def _help():
270     "Supply interactive help"
271     help = None
272     try:
273     help = windows[b[8]].help(b[9])
274     except KeyError:
275     # must be a menu
276     bb = swi.block(20)
277     # Wimp_GetMenuState
278     swi.swi(0x400f4, '0b', bb)
279     submenus = ''
280     for selection in bb:
281     if selection == -1:
282     break
283     submenus = submenus + '.' + str(selection)
284     if m.has_key('help.menu.' + current_menu.name + submenus):
285     help = m['help.menu.' + current_menu.name + submenus]
286     elif m.has_key('help.menu.' + current_menu.name):
287     help = m['help.menu.' + current_menu.name]
288     if help is None:
289     help = m['help.default']
290     b[0] = 256
291     b[3] = b[2]
292     b[4] = 0x503
293     b.padstring(help, '\0', 20, 256)
294     # Wimp_SendMessage
295     swi.swi(0x400e7, 'ibi', 17, b, b[1])
296    
297    
298     def screen_size():
299     "Return the screen width and height"
300     bb = swi.block(20)
301     bb[0] = 130
302     bb[1] = 131
303     bb[2] = -1
304     # OS_ReadVduVariables
305     swi.swi(0x31, 'bb', bb, bb)
306     # OS_ReadModeVariable
307     width = (bb[0] + 1) * 2 ** swi.swi(0x35, '-4;..i')
308     height = (bb[1] + 1) * 2 ** swi.swi(0x35, '-5;..i')
309     return width, height
310    
311    
312     def mem_string(address, write = None):
313     "Read or write a string in memory - dangerous"
314     if write is None:
315     bb = swi.register(256, address)
316     return bb.ctrlstring()
317     else:
318     bb = swi.register((len(write) + 4) / 4, address)
319     bb.padstring(write, '\0', 0, len(write) + 1)
320    
321    
322     def hourglass_on():
323     "Start the hourglass"
324     # Hourglass_On
325     swi.swi(0x406c0, '')
326    
327     def hourglass_off():
328     "Stop the hourglass"
329     # Hourglass_Off
330     swi.swi(0x406c1, '')
331    
332     def hourglass_percentage(value):
333     "Show a percentage under the hourglass"
334     # Hourglass_Percentage
335     swi.swi(0x406c4, 'i', value)
336    
337    
338     def warning(message):
339     "Give a warning error box"
340     # Wimp_ReportError
341     swi.swi(0x400df, 's1s', ' ' + message, task_name)
342    
343    
344     def lookup(message):
345     "Lookup message if possible"
346     try:
347     return m[message]
348     except KeyError:
349     return message
350    
351    
352     def send_message(event, action, receiver, data, icon = -1, ref = 0):
353     "Send a wimp message"
354     b[3] = ref
355     b[4] = action
356     offset = 5
357     for thing in data:
358     if isinstance(thing, types.IntType):
359     b[offset] = thing
360     offset = offset + 1
361     else:
362     length = int((len(thing) + 4) / 4)
363     b.padstring(thing, '\0', offset * 4, (offset + length) * 4)
364     offset = offset + length
365     break
366     b[0] = offset * 4
367     # Wimp_SendMessage
368     swi.swi(0x400e7, 'ibii', event, b, receiver, icon)
369    
370    
371     def close_menus():
372     # Wimp_CreateMenu
373     swi.swi(0x400d4, '.-')
374    
375    
376     def set_colour(colour):
377     swi.swi('ColourTrans_SetGCOL', 'i..00', colour << 8)
378    
379    
380     def plot(type, x, y):
381     # OS_Plot
382     swi.swi(0x45, 'iii', type, x, y)
383    
384    
385     def pointer():
386     bb = swi.block(20)
387     # Wimp_GetPointerInfo
388     swi.swi(0x400cf, '.b', bb)
389     return bb[0], bb[1], bb[2], bb[3], bb[4]
390    
391    
392     class window:
393     "Wimp window class"
394    
395     def __init__(self, id):
396     "Load window from template file"
397     # Wimp_OpenTemplate
398     try:
399     swi.swi(0x400d9, '.s', directory + '.Resources.' + territory + '.Templates')
400     except swi.error:
401     swi.swi(0x400d9, '.s', directory + '.Resources.UK.Templates')
402     # Wimp_LoadTemplate
403     buffer_size, workspace_size, found = swi.swi(0x400db, '.0..-s0;.ii...i', id)
404     if not found:
405     raise RuntimeError, m['error.template'] % id
406     buffer = swi.block(buffer_size / 4 + 1)
407     workspace = swi.block(workspace_size / 4 + 1)
408     swi.swi(0x400db, '.bbe-s0', buffer, workspace, workspace, id)
409     # Wimp_CreateWindow
410     self.handle = swi.swi(0x400c1, '.b;i', buffer)
411     self.workspace = workspace
412     self.name = id
413     self.title_pointer = buffer[18]
414     self.icons = buffer[21]
415     self.is_open = 0
416     # Wimp_CloseTemplate
417     swi.swi(0x400da, '')
418     windows[self.handle] = self
419     self.contents = []
420    
421     def remove(self):
422     del windows[self.handle]
423     bb = swi.block(20)
424     bb[0] = self.handle
425     # Wimp_DeleteWindow
426     swi.swi(0x400c3, '.b', bb)
427    
428     def do_redraw(self):
429     # Wimp_RedrawWindow
430     more = swi.swi(0x400c8, '.b;i', b)
431     self.ox = b[1] - b[5]
432     self.oy = b[4] - b[6]
433     self.redraw_start()
434     while more:
435     self.gx0 = b[7] - self.ox
436     self.gy0 = self.oy - b[10]
437     self.gx1 = b[9] - self.ox
438     self.gy1 = self.oy - b[8]
439     self.redraw()
440     # Wimp_GetRectangle
441     more = swi.swi(0x400ca, '.b;i', b)
442     self.redraw_stop()
443    
444     def redraw_start(self):
445     "Get ready to redraw the window"
446    
447     def redraw(self):
448     "Redraw a rectangle of the window"
449     if self.contents:
450     for content in self.contents:
451     if not ((content.pos[2] < self.gx0) or (content.pos[0] > self.gx1) or (content.pos[3] < self.gy0) or (content.pos[1] > self.gy1)):
452     content.redraw(self.ox, self.oy)
453    
454     def redraw_stop(self):
455     "Tidy up after redrawing the window"
456    
457     def open(self, minx = None, miny = None, maxx = None, maxy = None,
458     scrollx = None, scrolly = None, behind = None):
459     "Open the window"
460     bb = swi.block(20)
461     bb[0] = self.handle
462     if minx is None:
463     # Wimp_GetWindowState
464     swi.swi(0x400cb, '.b', bb)
465     else:
466     bb[1] = minx
467     bb[2] = miny
468     bb[3] = maxx
469     bb[4] = maxy
470     bb[5] = scrollx
471     bb[6] = scrolly
472     bb[7] = behind
473     # Wimp_OpenWindow
474     swi.swi(0x400c5, '.b', bb)
475     self.is_open = 1
476    
477     def open_centred(self, behind = None):
478     "Open the window centred in the screen"
479     bb = swi.block(20)
480     bb[0] = self.handle
481     # Wimp_GetWindowState
482     swi.swi(0x400cb, '.b', bb)
483     screen_width, screen_height = screen_size()
484     width = bb[3] - bb[1]
485     height = bb[4] - bb[2]
486     if behind is None:
487     behind = bb[7]
488     self.open((screen_width - width) / 2, (screen_height - height) / 2,
489     (screen_width + width) / 2, (screen_height + height) / 2,
490     bb[5], bb[6], behind)
491    
492 james 74 def open_full_size(self, behind = None):
493     bb = swi.block(20)
494     bb[0] = self.handle
495     swi.swi('Wimp_GetWindowState', '.b', bb)
496     bb[3] = 10000
497     bb[2] = -10000
498     if behind:
499     bb[7] = behind
500     swi.swi('Wimp_OpenWindow', '.b', bb)
501     self.is_open = 1
502    
503 james 17 def open_as_menu(self):
504     "Open the window as a menu centred on the pointer"
505     bb = swi.block(20)
506     bb[0] = self.handle
507     # Wimp_GetWindowState
508     swi.swi(0x400cb, '.b', bb)
509     width = bb[3] - bb[1]
510     height = bb[4] - bb[2]
511     # Wimp_GetPointerInfo
512     swi.swi(0x400cf, '.b', bb)
513     # Wimp_CreateMenu
514     swi.swi(0x400d4, '.iii', self.handle, bb[0] - width / 2, bb[1] + height / 2)
515    
516     def close(self):
517     "Close the window"
518     bb = swi.block(20)
519     bb[0] = self.handle
520     # Wimp_CloseWindow
521     swi.swi(0x400c6, '.b', bb)
522     self.is_open = 0
523 james 74
524 james 17 def entering(self):
525     "Pointer entering window"
526 james 74
527 james 17 def leaving(self):
528     "Pointer leaving window"
529 james 74
530 james 17 def click(self, icon, buttons, x, y, sx, sy):
531     "Mouse click in window"
532     self.contents.reverse()
533     if self.contents:
534     for content in self.contents:
535     if content.pos[0] <= x <= content.pos[2] and content.pos[1] <= y <= content.pos[3]:
536     if content.click(buttons, x - content.pos[0], y - content.pos[1]):
537     self.contents.reverse()
538     return 1
539     self.contents.reverse()
540     return 0
541 james 74
542 james 17 def help(self, icon):
543     "Return interactive help message"
544     if m.has_key('help.' + self.name + '.' + str(icon)):
545     return m['help.' + self.name + '.' + str(icon)]
546     elif m.has_key('help.' + self.name):
547     return m['help.' + self.name]
548     return None
549 james 74
550 james 17 def key(self, icon, key):
551     "Handle key presses"
552     # Wimp_ProcessKey
553     swi.swi(0x400dc, 'i', key)
554 james 74
555 james 17 def icon_text(self, icon, text = None):
556     "Read or write an icon's text contents"
557     if icon >= self.icons:
558     raise RuntimeError, 'Icon does not exist in icon_text()'
559     bb = swi.block(20)
560     bb[0] = self.handle
561     bb[1] = icon
562     # Wimp_GetIconState
563     swi.swi(0x400ce, '.b', bb)
564     indirected = bb[6] & 0x100
565     if text is None:
566     if indirected:
567     return mem_string(bb[7])
568     else:
569     return mem_string(bb.start + 28)
570     else:
571     if not indirected:
572     raise RuntimeError, 'Icon not indirected in icon_text()'
573     mem_string(bb[7], text)
574     # Wimp_SetIconState
575     bb[2] = bb[3] = 0
576     swi.swi(0x400cd, '.b', bb)
577     # Wimp_GetCaretPosition
578     swi.swi(0x400d3, '.b', bb)
579     if bb[0] == self.handle and bb[1] == icon:
580     self.put_caret(icon)
581 james 74
582 james 17 def icon_crement(self, icon, min, max, step = 1):
583     "Increment or decrement a numerical icon"
584     number = self.icon_text(icon)
585     if number == '':
586     number = (step < 0) * min + (step > 0) * max
587     else:
588     number = eval(number)
589     if number == (step < 0) * min + (step > 0) * max:
590     return
591     if number > max:
592     number = max
593     elif number < min:
594     number = min
595     else:
596     number = number + step
597     self.icon_text(0, str(number))
598 james 74
599 james 17 def icon_set(self, icon, state = None):
600     "Return the state or set the state of an icon"
601     if icon >= self.icons:
602     raise RuntimeError, 'Icon does not exist in icon_set()'
603     bb = swi.block(20)
604     bb[0] = self.handle
605     bb[1] = icon
606     # Wimp_GetIconState
607     swi.swi(0x400ce, '.b', bb)
608     if state is None:
609     return (bb[6] & 0x200000) == 0x200000
610     else:
611     bb[6] = bb[6] & ~0x200000
612     if state:
613     bb[6] = bb[6] | 0x200000
614     # Wimp_SetIconState
615     bb[2] = bb[3] = bb[6]
616     swi.swi(0x400cd, '.b', bb)
617 james 74
618 james 17 def put_caret(self, icon = -1, x = 0, y = 0, height = 0x2000000):
619     "Place the caret in the window"
620     if icon == -1:
621     # Wimp_SetCaretPosition
622     swi.swi(0x400d2, 'iiiii0', self.handle, icon, x, y, height)
623     else:
624     # Wimp_SetCaretPosition
625     swi.swi(0x400d2, 'ii00-i', self.handle, icon, len(self.icon_text(icon)))
626    
627     def icon_grey(self, icon, grey = 1):
628     "(Un)grey out an icon"
629     if icon >= self.icons:
630     raise RuntimeError, 'Icon does not exist in icon_grey()'
631     bb = swi.block(20)
632     bb[0] = self.handle
633     bb[1] = icon
634     bb[2] = 0x400000 * grey
635     bb[3] = 0x400000
636     swi.swi(0x400cd, '.b', bb)
637    
638     def icon_ungrey(self, icon):
639     "Ungrey an icon"
640     self.icon_grey(icon, 0)
641 james 74
642 james 17 def add_content(self, content):
643     "Add a content class to the window"
644     self.contents.append(content)
645 james 74
646 james 17 def remove_content(self, content):
647     "Remove a content class from the window"
648     self.contents.remove(content)
649 james 74
650 james 17 def refresh(self, x0, y0, x1, y1):
651     "Force a redraw of an area of the window"
652     swi.swi('Wimp_ForceRedraw', 'iiiii', self.handle, x0, -y1, x1, -y0)
653 james 74
654 james 17 def update(self, x0, y0, x1, y1):
655     "Update an area of the window"
656     bb = swi.block(20)
657     bb[0] = self.handle
658     bb[1] = x0
659     bb[2] = -y1
660     bb[3] = x1
661     bb[4] = -y0
662     # Wimp_UpdateWindow
663     more = swi.swi(0x400c9, '.b;i', bb)
664     self.ox = bb[1] - bb[5]
665     self.oy = bb[4] - bb[6]
666     self.redraw_start()
667     while more:
668     self.gx0 = bb[7] - self.ox
669     self.gy0 = self.oy - bb[10]
670     self.gx1 = bb[9] - self.ox
671     self.gy1 = self.oy - bb[8]
672     self.redraw()
673     # Wimp_GetRectangle
674     more = swi.swi(0x400ca, '.b;i', bb)
675     self.redraw_stop()
676 james 74
677 james 17 def title(self, title = None):
678     "Change or return the window title"
679     if title is None:
680     return mem_string(self.title_pointer, None)
681     else:
682     mem_string(self.title_pointer, title)
683     if self.is_open:
684     bb = swi.block(20)
685     # Wimp_GetCaretPosition
686     swi.swi(0x400d3, '.b', bb)
687     if bb[0] == self.handle:
688     # Wimp_SetCaretPosition
689     swi.swi(0x400d2, '-000--')
690     swi.swi(0x400d2, 'iiiiii', bb[0], bb[1], bb[2], bb[3], bb[4], bb[5])
691     else:
692     # Wimp_SetCaretPosition
693     swi.swi(0x400d2, 'i-00i-', self.handle, 1<<25)
694     swi.swi(0x400d2, 'iiiiii', bb[0], bb[1], bb[2], bb[3], bb[4], bb[5])
695    
696     def set_extent(self, x0, y0, x1, y1):
697     "Set the work area extent in os units"
698     bb = swi.block(20)
699     bb[0] = x0
700     bb[1] = -y1
701     bb[2] = x1
702     bb[3] = -y0
703     # Wimp_SetExtent
704     swi.swi(0x400d7, 'ib', self.handle, bb)
705 james 74
706 james 17 def origin(self):
707     "Return the window origin position on the screen"
708     bb = swi.block(20)
709     bb[0] = self.handle
710     swi.swi('Wimp_GetWindowState', '.b', bb)
711     self.ox = b[1] - b[5]
712     self.oy = b[4] - b[6]
713     return self.ox, self.oy
714 james 74
715 james 17 def icon_box(self, icon):
716     "Return the bbox of an icon relative to the window"
717     bb = swi.block(20)
718     bb[0] = self.handle
719     bb[1] = icon
720     swi.swi('Wimp_GetIconState', '.b', bb)
721     return bb[2], -bb[5], bb[4], -bb[3]
722 james 74
723 james 17 def icon_drag(self, icon):
724     "Start to drag the specified icon"
725     self.origin()
726     x0, y0, x1, y1 = self.icon_box(icon)
727     bb = swi.block(20)
728     bb[0] = self.ox + x0
729     bb[1] = self.oy - y1
730     bb[2] = self.ox + x1
731     bb[3] = self.oy - y0
732     swi.swi('DragASprite_Start', 'i1sb', 0xc5, self.icon_text(icon), bb)
733    
734    
735     class iconbar_icon(window):
736 james 74
737 james 17 def __init__(self, sprite, handler, indirected = 0):
738     "Create an iconbar icon with the specified sprite"
739     bb = swi.block(20)
740     bb[0] = -1
741     bb[1] = bb[2] = 0
742     bb[3] = bb[4] = 68
743     if indirected:
744     bb[5] = 0x311a
745     self.buffer = swi.block(5)
746     self.buffer.padstring(sprite, '\0')
747     bb[6] = self.buffer.start
748     bb[7] = 1
749     bb[8] = len(sprite)
750     else:
751     bb[5] = 0x301a
752     bb.padstring(sprite, '\0', 24, 36)
753     # Wimp_CreateIcon
754     self.icon = swi.swi(0x400c2, '0b;i', bb)
755     self.icons = self.icon + 1
756     self.handle = -2
757     self.name = 'iconbar'
758     self.handler = handler
759     windows[-2] = self
760 james 74
761 james 17 def click(self, icon, buttons, x, y, sx, sy):
762     "Handle a click on the iconbar"
763     self.handler(buttons, sx)
764 james 74
765 james 17 def change(self, sprite):
766     self.icon_text(self.icon, sprite)
767    
768    
769     class info_window(window):
770     "Info box window including email/web buttons"
771 james 74
772 james 17 def __init__(self):
773     "Load the window template"
774     window.__init__(self, 'info')
775 james 74
776 james 17 def click(self, icon, buttons, x, y, sx, sy):
777     "Handle mouse clicks"
778     try:
779     if icon == 8 or icon == 9:
780     if os.path.exists('System:Modules.Network.URI'):
781     # Wimp_StartTask
782     swi.swi(0x400de, 's', 'RMEnsure AcornURI 0.00 RMRun System:Modules.Network.URI')
783     if icon == 8:
784     # URI_Dispatch
785     swi.swi(0x4e381, '0s0', m['email'])
786     elif icon == 9:
787     # URI_Dispatch
788     swi.swi(0x4e381, '0s0', m['www'])
789     except swi.error:
790     swi.swi(0x107, '')
791    
792    
793     class save_window(window):
794     "Standard RISC OS save window"
795 james 74
796 james 17 def __init__(self, handler, type):
797     "Load the window template"
798     window.__init__(self, 'save')
799     self.handler = handler
800     self.type = type
801 james 74
802 james 17 def click(self, icon, buttons = 0, x = 0, y = 0, sx = 0, sy = 0):
803     "Handle mouse clicks"
804     if icon == 0 and buttons > 15:
805     if self.icon_text(1) == '':
806     warning(m['error.save.empty'])
807     else:
808     self.drag_save()
809     elif icon == 2:
810     close_menus()
811     self.close()
812     elif icon == 3:
813     path = self.icon_text(1)
814     if os.path.isabs(path):
815     self.handler(path, 0)
816     close_menus()
817     self.close()
818     else:
819     warning(m['error.save.tosave'])
820    
821     def key(self, icon, key):
822     "Handle key presses"
823     if key == 0xd:
824     self.click(3)
825     elif key == 0x1b:
826     self.click(2)
827     else:
828     # Wimp_ProcessKey
829     swi.swi(0x400dc, 'i', key)
830    
831     def path(self, path):
832     "Set the save path"
833     self.icon_text(1, path)
834    
835     def drag_save(self):
836     "Save the object by dragging the icon"
837     self.icon_drag(0)
838     while 1:
839     event, details = poll()
840     if event == 7:
841     swi.swi('DragASprite_Stop', '')
842     mouse = pointer()
843     path = self.icon_text(1)
844     # Message_DataSave
845     send_message(18, 1, mouse[3],
846     (mouse[3], mouse[4], mouse[0], mouse[1], 0, self.type, os.path.basename(path)),
847     mouse[4])
848     break
849     else:
850     handle((event, details))
851     event, details = poll()
852     if event == 19 and details == 1:
853     # Message_DataSave bounced
854     pass
855     elif event in (17, 18) and details == 2:
856     path = b.nullstring(44, b[0])
857     # Message_DataSaveAck
858     if self.handler(path, b[9] == -1):
859     # Message_DataLoad
860     send_message(17, 3, b[1], (b[5], b[6], b[7], b[8], b[9], b[10], path), ref = b[2])
861     close_menus()
862     self.close()
863     else:
864     handle((event, details))
865    
866    
867     class menu:
868     "Wimp menu class"
869 james 74
870 james 17 def __init__(self, name, items):
871     "Load menu"
872     self.blocks = []
873     self.menu_block = self._load(items)
874     self.name = name
875 james 74
876 james 17 def menu(self, x, y):
877     "Display menu and return the selection"
878     global current_menu
879     current_menu = self
880     swi.swi(0x400d4, '.bii', self.menu_block, x, y)
881     while 1:
882     event, details = poll(1)
883     # Menu_Selection
884     if event == 9:
885     bb = swi.block(20)
886     # Wimp_GetPointerInfo
887     swi.swi(0x400cf, '.b', bb)
888     return details, (bb[2] == 1)
889     # User_Message, User_Message_Recorded: Message_MenusDeleted
890     elif (event == 17 or event == 18) and details == 0x400c9:
891     return None, 0
892     else:
893     handle((event, details))
894    
895     def popup(self, window, icon):
896     "Popup the menu next to the icon"
897     bb = swi.block(20)
898     bb[0] = window.handle
899     bb[1] = icon
900     # Wimp_GetIconState
901     swi.swi(0x400ce, '.b', bb)
902     x = bb[4]
903     y = bb[5]
904     # Wimp_GetWindowState
905     swi.swi(0x400cb, '.b', bb)
906     return self.menu(bb[1] - bb[5] + x, bb[4] - bb[6] + y)
907    
908     def _load(self, items):
909     "Create a wimp menu structure (recursively)"
910     menu_block = swi.block(7 + 6 * (len(items) - 1))
911     self.blocks.append(menu_block)
912 james 74
913 james 17 title_block = swi.block(len(lookup(items[0])) / 4 + 1)
914     self.blocks.append(title_block)
915     title_block.padstring(lookup(items[0]), '\0')
916     menu_block[0] = title_block.start
917 james 74
918 james 17 menu_block[1] = menu_block[2] = -1
919     menu_block[3] = 0x00070207
920     menu_block[4] = 300
921     menu_block[5] = 44
922     menu_block[6] = 0
923 james 74
924 james 17 offset = 7
925 james 74
926 james 17 for item in items[1:]:
927     if isinstance(item, types.StringType):
928     text = item
929     submenu = -1
930     else:
931     text = item[0]
932     if isinstance(item[1], window):
933     submenu = item[1].handle
934     else:
935     submenu = self._load(item[1]).start
936 james 74 grey = False
937     if text[0] == '-':
938     grey = True
939     text = text[1:]
940 james 17
941     item_block = swi.block(len(lookup(text)) / 4 + 1)
942     self.blocks.append(item_block)
943     item_block.padstring(lookup(text), '\0')
944     menu_block[offset] = (0x100 * (offset == 7))
945     menu_block[offset + 1] = submenu
946     menu_block[offset + 2] = 0x07000111
947 james 74 if grey:
948     menu_block[offset + 2] |= 1 << 22
949 james 17 menu_block[offset + 3] = item_block.start
950     menu_block[offset + 4] = menu_block[offset + 5] = -1
951 james 74
952 james 17 offset = offset + 6
953 james 74
954 james 17 menu_block[offset - 6] = menu_block[offset - 6] | 0x80
955 james 74
956 james 17 return menu_block
957    
958    
959     class content:
960     "Base window content class"
961 james 74
962 james 17 def __init__(self, w, x0, y0, x1, y1):
963     "Initialise a window content"
964     self.window = w
965     self.pos = [x0, y0, x1, y1]
966 james 74
967 james 17 def redraw(self, ox, oy):
968     "Redraw content"
969     pass
970 james 74
971 james 17 def click(self, buttons, x, y):
972     "Handle clicks in the content"
973     return 0
974 james 74
975 james 17 def refresh(self):
976     "Force redraw of the content"
977 james 74 self.window.refresh(self.pos[0] - 4, self.pos[1] - 4,
978     self.pos[2] + 4, self.pos[3] + 4)
979    
980 james 17 def update(self):
981     "Update the content"
982 james 74 self.window.update(self.pos[0] - 4, self.pos[1] - 4,
983     self.pos[2] + 4, self.pos[3] + 4)
984 james 17
985    
986     class rectangle(content):
987     "Example window content"
988    
989 james 74 def __init__(self, w, x0, y0, x1, y1, fill = None, border = None,
990     text = None, font = None, text_colour = 0x000000):
991 james 17 content.__init__(self, w, x0, y0, x1, y1)
992     self.fill = fill
993     self.border = border
994 james 74 self.text = text
995     self.font = font
996     self.text_colour = text_colour
997    
998 james 17 def redraw(self, ox, oy):
999     if not self.fill is None:
1000     # ColourTrans_SetGCOL
1001     swi.swi(0x40743, 'i..i0', self.fill << 8, 1 << 8)
1002     # OS_Plot
1003     swi.swi(0x45, '4ii', ox + self.pos[0], oy - self.pos[3])
1004     swi.swi(0x45, 'iii', 96 + 5, ox + self.pos[2], oy - self.pos[1])
1005     if not self.border is None:
1006     # ColourTrans_SetGCOL
1007     swi.swi(0x40743, 'i..i0', self.border << 8, 1 << 8)
1008     # OS_Plot
1009     swi.swi(0x45, '4ii', ox + self.pos[0], oy - self.pos[3])
1010     swi.swi(0x45, '5ii', ox + self.pos[2], oy - self.pos[3])
1011     swi.swi(0x45, '5ii', ox + self.pos[2], oy - self.pos[1])
1012     swi.swi(0x45, '5ii', ox + self.pos[0], oy - self.pos[1])
1013     swi.swi(0x45, '5ii', ox + self.pos[0], oy - self.pos[3])
1014 james 74 if not self.text is None and not self.font is None:
1015     self.font.colour(0xdddddd, self.text_colour)
1016     self.font.paint(self.text, 400 * (ox + self.pos[0]),
1017     400 * (oy - (self.pos[1] + self.pos[3] * 3) / 4))
1018 james 17
1019     def click(self, buttons, x, y):
1020 james 74 #self.window.remove_content(self)
1021     #self.refresh()
1022     return 0
1023 james 17
1024    
1025 james 74 class paragraph(content):
1026     "A paragraph of text"
1027    
1028     def __init__(self, w, x0, y0, x1, font_family, font_style, font_size,
1029     text, text_colour = 0x000000, fill = None, padding = 10):
1030     self.font_family = font_family
1031     self.font_style = font_style
1032     self.font_size = font_size
1033     self.line_height = int(font_size * 0.2)
1034     self.text = []
1035     self.text_colour = text_colour
1036     self.fill = fill
1037     self.padding = padding
1038     text = text.encode('utf8')
1039     width = x1 - x0 - padding - padding
1040     while text:
1041     (c, x) = rufl.split(font_family, font_style, font_size, text,
1042     width)
1043     s = text[:c]
1044     space = s.rfind(' ')
1045     if c != len(text) and space != -1:
1046     c = space
1047     s = text[:c + 1]
1048     text = text[c + 1:]
1049     self.text.append(s)
1050     y1 = y0 + len(self.text) * self.line_height + padding + padding
1051     content.__init__(self, w, x0, y0, x1, y1)
1052     self.y1 = y1
1053    
1054     def redraw(self, ox, oy):
1055     if not self.fill is None:
1056     swi.swi('ColourTrans_SetGCOL', 'i..i0', self.fill << 8, 1 << 8)
1057     swi.swi('OS_Plot', '4ii', ox + self.pos[0], oy - self.pos[3])
1058     swi.swi('OS_Plot', 'iii', 96 + 5, ox + self.pos[2],
1059     oy - self.pos[1])
1060     swi.swi('ColourTrans_SetFontColours', 'iiii', 0,
1061     0xffffff << 8, self.text_colour << 8, 14)
1062     y = oy - self.pos[1] - self.padding - self.line_height * 0.75
1063     for line in self.text:
1064     rufl.paint(self.font_family, self.font_style, self.font_size,
1065     line, ox + self.pos[0] + self.padding, y, rufl.blend)
1066     y -= self.line_height
1067    
1068    
1069     def font(name, size_x, size_y, no_encoding = False):
1070 james 17 f = (name, size_x, size_y)
1071     if fonts.has_key(f):
1072     font = fonts[f]
1073     font.use = font.use + 1
1074     return font
1075     else:
1076 james 74 fonts[f] = _font(name, size_x, size_y, no_encoding)
1077 james 17 return fonts[f]
1078    
1079     class _font:
1080    
1081 james 74 def __init__(self, name, size_x, size_y, no_encoding):
1082 james 17 self.name = name
1083     self.size_x = size_x
1084     self.size_y = size_y
1085     self.use = 1
1086 james 74 if no_encoding:
1087     self.handle = swi.swi('XFont_FindFont', '.sii00;i',
1088     self.name, self.size_x, self.size_y)
1089     self.utf8 = False
1090     else:
1091     try:
1092     self.handle = swi.swi('XFont_FindFont', '.sii00;i',
1093     self.name + '\EUTF8', self.size_x, self.size_y)
1094     self.utf8 = True
1095     except swi.error:
1096     self.handle = swi.swi('XFont_FindFont', '.sii00;i',
1097     self.name + '\ELatin1', self.size_x, self.size_y)
1098     self.utf8 = False
1099    
1100 james 17 def lose(self):
1101     self.use = self.use - 1
1102     if self.use == 0:
1103     # Font_LoseFont
1104     swi.swi(0x40082, 'i', self.handle)
1105    
1106     def paint(self, string, x, y, spacex = 0, spacey = 0, offx = 0, offy = 0,
1107     x0 = 0, y0 = 0, x1 = 0, y1 = 0):
1108 james 74 if self.utf8:
1109     s = string.encode('utf8')
1110     else:
1111     s = string.encode('latin1', 'replace')
1112 james 17 if x0:
1113     bb = swi.block(20)
1114     bb[0] = spacex
1115     bb[1] = spacey
1116     bb[2] = offx
1117     bb[3] = offy
1118     bb[4] = int(x0)
1119     bb[5] = int(y0)
1120     bb[6] = int(x1)
1121     bb[7] = int(y1)
1122 james 74 swi.swi('Font_Paint', 'isiiib', self.handle, s, 0x322, x, y, bb)
1123     else:
1124     swi.swi('Font_Paint', 'isiii', self.handle, s, 0x300, x, y)
1125 james 17
1126     def charbbox(self, char):
1127     return swi.swi('Font_CharBBox', 'ii0;.iiii', self.handle, ord(char))
1128 james 74
1129 james 17 def colour(self, back, fore):
1130     # ColourTrans_SetFontColours
1131     swi.swi(0x4074f, 'iiii', self.handle, back << 8, fore << 8, 14)
1132    
1133     def stringbox(self, string):
1134     if string == '':
1135     return (0, 0, 0, 0)
1136     if string[-1] == ' ':
1137     string = string + ' '
1138 james 74 if self.utf8:
1139     s = string.encode('utf8')
1140     else:
1141     s = string.encode('latin1', 'replace')
1142 james 17 bb = swi.block(20)
1143     bb[0] = bb[1] = bb[2] = bb[3] = 0
1144     bb[4] = -1
1145     # Font_ScanString
1146 james 74 swi.swi(0x400a1, 'isiiib', self.handle, s, 0x40320, 0x7fffffff,
1147     0x7fffffff, bb)
1148 james 17 return bb[5], bb[6], bb[7], bb[8]
1149    
1150     def caretpos(self, string, x, y):
1151 james 74 if self.utf8:
1152     s = string.encode('utf8')
1153     else:
1154     s = string.encode('latin1', 'replace')
1155     bb = swi.block(len(s) / 4 + 10)
1156     bb.padstring(s, chr(0))
1157 james 17 # Font_ScanString
1158 james 74 pos, cx, cy = swi.swi(0x400a1, 'ibiii;.i.ii', self.handle, bb,
1159     0x20300, x, y)
1160 james 17 return pos - bb.start, cx, cy
1161 james 74
1162 james 17 def caretxy(self, string, pos):
1163 james 74 if self.utf8:
1164     s = string.encode('utf8')
1165     else:
1166     s = string.encode('latin1', 'replace')
1167 james 17 # Font_ScanString
1168 james 74 return swi.swi(0x400a1, 'isiii..i;...ii', self.handle, s,
1169     0x20380, 0x7fffffff, 0x7fffffff, pos)
1170    
1171 james 17 def fontbox(self):
1172     return swi.swi('Font_ReadInfo', 'i;.iiii', self.handle)
1173 james 74
1174 james 17 def split(self, string, x, y):
1175 james 74 if self.utf8:
1176     s = string.encode('utf8')
1177     else:
1178     s = string.encode('latin1', 'replace')
1179     bb = swi.block(len(s) / 4 + 10)
1180     bb.padstring(s, chr(0))
1181 james 17 # Font_ScanString
1182     pos = swi.swi(0x400a1, 'ibiii;.i', self.handle, bb, 0x300, x, y)
1183     return pos - bb.start
1184    
1185    
1186     class document:
1187    
1188     def __init__(self, width, height, margin):
1189     self.contents = []
1190     self.views = []
1191     self.extent = [width, height]
1192     self.margin = margin
1193     self.fonts = {}
1194     self.fonts_use = {}
1195    
1196     def add_content(self, content):
1197     self.contents.append(content)
1198     for view in self.views:
1199     content.add_view(view)
1200    
1201     def remove_content(self, content):
1202     self.contents.remove(content)
1203    
1204     def add_view(self, view):
1205     self.views.append(view)
1206     for content in self.contents:
1207     content.add_view(view)
1208    
1209     def remove_view(self, view):
1210     self.views.remove(view)
1211     for content in self.contents:
1212     content.remove_view(view)
1213 james 74
1214 james 17 def rescaled(self, view):
1215     for content in self.contents:
1216     content.remove_view(view)
1217     content.add_view(view)
1218    
1219     def redraw_start(self, view, scale):
1220     pass
1221    
1222     def redraw(self, view, scale):
1223     if self.margin:
1224     self.redraw_margin(view)
1225     for content in self.contents:
1226     content.scaled_pos = map(lambda z, scale = scale: scale * z / 400, content.pos)
1227     if not ((content.scaled_pos[2] < view.gx0) or (content.scaled_pos[0] > view.gx1) or (content.scaled_pos[3] < view.gy0) or (content.scaled_pos[1] > view.gy1)):
1228     content.redraw_box = [view.gx0 * 400 / scale - content.pos[0],
1229     view.gy0 * 400 / scale - content.pos[1],
1230     view.gx1 * 400 / scale - content.pos[0],
1231     view.gy1 * 400 / scale - content.pos[1]]
1232     content.redraw(view, view.ox, view.oy, scale)
1233 james 74
1234 james 17 def redraw_stop(self, view, scale):
1235     pass
1236 james 74
1237 james 17 def redraw_margin(self, view):
1238     set_colour(0x777777)
1239     plot(4, view.ox + view.gx0, view.oy)
1240     plot(97, view.gx1 - view.gx0, self.margin)
1241     plot(4, view.ox + view.gx0, view.oy - view.extent[1])
1242     plot(97, view.gx1 - view.gx0, -self.margin)
1243     plot(4, view.ox, view.oy - view.gy1)
1244     plot(97, -self.margin, view.gy1 - view.gy0)
1245     plot(4, view.ox + view.extent[0], view.oy - view.gy1)
1246     plot(97, self.margin, view.gy1 - view.gy0)
1247    
1248     def click(self, view, buttons, x, y):
1249     for content in self.contents:
1250     if content.pos[0] <= x <= content.pos[2] and content.pos[1] <= y <= content.pos[3]:
1251     content.click(view, buttons, x - content.pos[0], y - content.pos[1])
1252 james 74
1253 james 17 def refresh(self, x0, y0, x1, y1):
1254     for view in self.views:
1255     view.update(int(view.scale * x0 / 400 - 4), int(view.scale * y0 / 400 - 4),
1256     int(view.scale * x1 / 400 + 4), int(view.scale * y1 / 400 + 4))
1257 james 74
1258 james 17 def add_font(self, name, size_x, size_y):
1259     f = (name, size_x, size_y)
1260     if self.fonts.has_key(f):
1261     self.fonts_use[f] = self.fonts_use[f] + 1
1262     else:
1263     self.fonts[f] = font(name, size_x, size_y)
1264     self.fonts_use[f] = 1
1265    
1266     def remove_font(self, name, size_x, size_y):
1267     f = (name, size_x, size_y)
1268     self.fonts_use[f] = self.fonts_use[f] - 1
1269     if self.fonts_use[f] == 0:
1270     self.fonts[f].lose()
1271     del self.fonts[f]
1272     del self.fonts_use[f]
1273    
1274    
1275     class document_view(window):
1276 james 74
1277 james 17 def __init__(self, document, id, scale):
1278     window.__init__(self, id)
1279     self.document = document
1280     self.fonts = {}
1281     self.rescale(scale)
1282     document.add_view(self)
1283     self.caret = None
1284    
1285     def redraw_start(self):
1286     self.document.redraw_start(self, self.scale)
1287    
1288     def redraw(self):
1289     self.document.redraw(self, self.scale)
1290 james 74
1291 james 17 def redraw_stop(self):
1292     self.document.redraw_stop(self, self.scale)
1293    
1294     def click(self, icon, buttons, x, y, sx, sy):
1295     if 0 <= x <= self.extent[0] and 0 <= y <= self.extent[1]:
1296     self.document.click(self, buttons, 400 * x / self.scale, 400 * y / self.scale)
1297    
1298     def key(self, icon, key):
1299     if self.caret:
1300     self.caret[0].key(self, key)
1301     else:
1302     swi.swi(0x400dc, 'i', key)
1303    
1304     def rescale(self, scale):
1305     for f in self.fonts.values():
1306     f.lose()
1307     self.fonts = {}
1308     for f in self.document.fonts.keys():
1309     self.fonts[f] = font(f[0], f[1] * scale, f[2] * scale)
1310     self.scale = scale
1311     self.extent = (int(scale * self.document.extent[0] / 400), int(scale * self.document.extent[1] / 400))
1312     self.set_extent(-self.document.margin, -self.document.margin,
1313     self.extent[0] + self.document.margin, self.extent[1] + self.document.margin)
1314     self.document.rescaled(self)
1315    
1316    
1317     class document_content:
1318 james 74
1319 james 17 def __init__(self, document, x0, y0, x1, y1):
1320     self.pos = [x0, y0, x1, y1]
1321     self.colour = 0x0000ff
1322     self.document = document
1323     document.add_content(self)
1324    
1325     def add_view(self, view):
1326     pass
1327    
1328     def remove_view(self, view):
1329     pass
1330 james 74
1331 james 17 def redraw(self, view, x, y, scale):
1332     set_colour(self.colour)
1333     plot(4, x + self.scaled_pos[0], y - self.scaled_pos[1])
1334     plot(21, x + self.scaled_pos[2], y - self.scaled_pos[1])
1335     plot(21, x + self.scaled_pos[2], y - self.scaled_pos[3])
1336     plot(21, x + self.scaled_pos[0], y - self.scaled_pos[3])
1337     plot(21, x + self.scaled_pos[0], y - self.scaled_pos[1])
1338     plot(21, x + self.scaled_pos[2], y - self.scaled_pos[3])
1339     plot(4, x + self.scaled_pos[2], y - self.scaled_pos[1])
1340     plot(21, x + self.scaled_pos[0], y - self.scaled_pos[3])
1341 james 74
1342 james 17 def click(self, view, buttons, x, y):
1343     self.colour = 0xffffff - self.colour
1344     self.document.refresh(self.pos[0], self.pos[1], self.pos[2], self.pos[3])

  ViewVC Help
Powered by ViewVC 1.1.26