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

Contents of /pythonwimp/wimp.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 75 - (show annotations) (download) (as text)
Sun May 15 12:10:21 2005 UTC (18 years, 11 months ago) by james
File MIME type: text/x-python
File size: 43505 byte(s)
Place under MIT license.

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

  ViewVC Help
Powered by ViewVC 1.1.26