"""Interface with RISC OS window manager (wimp) and related functions. Licensed under the MIT License, http://www.opensource.org/licenses/mit-license Copyright 2005 James Bursa Class hierarchy: window -- base window class iconbar_icon -- iconbar icon info_window -- info box save_window -- save box menu -- menu class content -- window contents class rectangle -- example content font -- font class Functions: task() -- initialise the task with the wimp quit() -- tidy up and exit poll() -- get a wimp event handle() -- handle a wimp event screen_size() -- return screen mode dimensions mem_string() -- read or write a string to a buffer hourglass_on() -- switch on the hourglass hourglass_off() -- switch off the hourglass hourglass_percentage() -- display a percentage underneath warning() -- display a wimp error box lookup() -- convert a message token to a message send_message() -- send a wimp message close_menus() -- close any open menus set_colour() -- set the colour for plotting plot() -- plot to the screen Variables: task_handle -- wimp task handle task_name -- wimp task name poll_mask -- default wimp poll event mask Comments: All window coordinates behave as follows: - the origin is assumed to be in the top left, so ensure that your templates have this to avoid problems - x coordinates are 0 at the origin and increase across the window - y coordinates are 0 at the origin and increase down the window - scroll offsets are also always positive Coordinates are usually in os units. However, the document classes and fonts use millipoints. At 100%, 400 millipoints = 1 os unit. Font sizes are in 1/16 points. """ import os import swi import string import sys import types import rufl task_handle = 0 task_name = 'Application' poll_mask = 1 null_reason_handler = None user_drag_handler = None message_handler = {} windows = {} fonts = {} _depth = 0 def task(name, dir, task, messages = (), debug = 0, profile = 0, mask = None): "Initialise the task with the wimp and load the messages file" global task_name, directory, b, wimp_version, task_handle, territory, m, task_module, _trace hourglass_on() task_name = name directory = dir task_module = task if mask: poll_mask = mask # set up debugging stuff if debug: import traceback sys.stderr = sys.stdout = open(directory + '.^.Output', 'w') if debug == 2: def _trace(frame, event, arg): global _depth if event == 'call': print _depth * ' ', frame.f_code.co_name + '(', arg, ')' _depth = _depth + 1 elif event == 'return': print _depth * ' ', 'return', arg _depth = _depth - 1 return _trace sys.settrace(_trace) elif debug == 3: def _trace(frame, event, arg): global _depth if event == 'line': print _depth * ' ', frame.f_lineno elif event == 'call': print _depth * ' ', frame.f_code.co_name + '(', arg, ')' _depth = _depth + 1 elif event == 'return': print _depth * ' ', 'return', arg _depth = _depth - 1 else: print _depth * ' ', event + ':', arg return _trace sys.settrace(_trace) # buffer for various stuff b = swi.block(64) # initialise with wimp # place the message list in the buffer b[0] = 0x502 # Message_HelpRequest b[1] = 0x400c9 # Message_MenusDeleted index = 2 for message in messages: b[index] = message index = index + 1 b[index] = 0 # Wimp_Initialise wimp_version, task_handle = swi.swi(0x400c0, 'iisb;ii', 310, 0x4b534154, name, b) # load messages # Territory_NumberToName, Territory_Number territory = swi.swi(0x43043, 'ibi;.s', swi.swi(0x43040, ';i'), b, 256) try: message_file = open(directory + '.Resources.' + territory + '.Messages', 'r') except IOError: message_file = open(directory + '.Resources.UK.Messages', 'r') m = {} for line in message_file.readlines(): if line[0] != '#' and ':' in line: message = string.split(line, ':', 1) m[message[0]] = message[1][:-1] message_file.close() try: if profile: import profile profile.run('wimp._go()', directory + '.^.Profile') else: _go() except SystemExit: hourglass_on() task_module.quit() except RuntimeError, value: swi.swi(0x400df, 'sis', ' ' + str(value), 1, task_name) except: if debug > 1: sys.settrace(None) type, value, traceb = sys.exc_info() swi.swi(0x400df, 'sis', ' Internal error - must exit (' + str(type) + ': ' + str(value) + ')', 1, task_name) if debug: traceback.print_exc() trace = traceback.extract_tb(traceb) try: swi.swi(0x42587, '') swi.swi(0x42588, '0.s', trace[-1][0]) swi.swi(0x42588, '1.si1s', trace[-1][0], trace[-1][1], str(type) + ': ' + trace[-1][2] + ': ' + trace[-1][3]) swi.swi(0x42589, '') except: pass if sys.stderr.tell() > 0: sys.stderr.close() os.system('Filer_Run ' + directory + '.^.Output') else: sys.stderr.close() # Wimp_CloseDown swi.swi(0x400dd, 'ii', task_handle, 0x4b534154) hourglass_off() def _go(): "Run the program" task_module.init() hourglass_off() while 1: handle(poll(poll_mask)) def quit(): "Trigger quitting" for f in fonts.values(): f.use = 1 f.lose() raise SystemExit def poll(mask = None): "Poll wimp once and return data" # Wimp_Poll if mask is None: mask = poll_mask event = swi.swi(0x400c7, 'ib;i', mask, b) if event == 2: # Open_Window_Request return 2, (windows[b[0]], b[1], b[2], b[3], b[4], b[5], b[6], b[7]) elif event == 3: # Close_Window_Request return 3, windows[b[0]] elif event == 6: # Mouse_Click x, y, z, w, i = b[0], b[1], b[2], b[3], b[4] b[0] = w # Wimp_GetWindowState swi.swi(0x400cb, '.b', b) return 6, (windows[w], i, z, x - (b[1] - b[5]), (b[4] - b[6]) - y, x, y) elif event == 7: # User_Drag_Box return 7, (b[0], b[1], b[2], b[3]) elif event == 8: # Key_Pressed return 8, (windows[b[0]], b[1], b[6]) # User_Message, User_Message_Recorded, User_Message_Acknowledge elif event == 17 or event == 18 or event == 19: return event, (b[4]) return event, b def handle((event, details)): "Handle events as returned by poll()" # try: if event == 0: # Null_Reason_Code if null_reason_handler: null_reason_handler() elif event == 1: # Redraw_Window_Request windows[b[0]].do_redraw() elif event == 2: # Open_Window_Request details[0].open(details[1], details[2], details[3], details[4], details[5], details[6], details[7]) elif event == 3: # Close_Window_Request details.close() elif event == 4: # Pointer_Leaving_Window if windows.has_key(b[0]): windows[b[0]].leaving() elif event == 5: # Pointer_Entering_Window windows[b[0]].entering() elif event == 6: # Mouse_Click details[0].click(details[1], details[2], details[3], details[4], details[5], details[6]) elif event == 7: # User_Drag_Box if user_drag_handler: user_drag_handler(details[0], details[1], details[2], details[3]) elif event == 8: # Key_Pressed details[0].key(details[1], details[2]) # User_Message, User_Message_Recorded, User_Message_Acknowledge elif event == 17 or event == 18 or event == 19: if details == 0: # Message_Quit quit() elif details == 0x502: # Message_HelpRequest _help() elif message_handler.has_key(details): message_handler[details]() # except KeyError: # pass def _help(): "Supply interactive help" help = None try: help = windows[b[8]].help(b[9]) except KeyError: # must be a menu bb = swi.block(20) # Wimp_GetMenuState swi.swi(0x400f4, '0b', bb) submenus = '' for selection in bb: if selection == -1: break submenus = submenus + '.' + str(selection) if m.has_key('help.menu.' + current_menu.name + submenus): help = m['help.menu.' + current_menu.name + submenus] elif m.has_key('help.menu.' + current_menu.name): help = m['help.menu.' + current_menu.name] if help is None: help = m['help.default'] b[0] = 256 b[3] = b[2] b[4] = 0x503 b.padstring(help, '\0', 20, 256) # Wimp_SendMessage swi.swi(0x400e7, 'ibi', 17, b, b[1]) def screen_size(): "Return the screen width and height" bb = swi.block(20) bb[0] = 130 bb[1] = 131 bb[2] = -1 # OS_ReadVduVariables swi.swi(0x31, 'bb', bb, bb) # OS_ReadModeVariable width = (bb[0] + 1) * 2 ** swi.swi(0x35, '-4;..i') height = (bb[1] + 1) * 2 ** swi.swi(0x35, '-5;..i') return width, height def mem_string(address, write = None): "Read or write a string in memory - dangerous" if write is None: bb = swi.register(256, address) return bb.ctrlstring() else: bb = swi.register((len(write) + 4) / 4, address) bb.padstring(write, '\0', 0, len(write) + 1) def hourglass_on(): "Start the hourglass" # Hourglass_On swi.swi(0x406c0, '') def hourglass_off(): "Stop the hourglass" # Hourglass_Off swi.swi(0x406c1, '') def hourglass_percentage(value): "Show a percentage under the hourglass" # Hourglass_Percentage swi.swi(0x406c4, 'i', value) def warning(message): "Give a warning error box" # Wimp_ReportError swi.swi(0x400df, 's1s', ' ' + message, task_name) def lookup(message): "Lookup message if possible" try: return m[message] except KeyError: return message def send_message(event, action, receiver, data, icon = -1, ref = 0): "Send a wimp message" b[3] = ref b[4] = action offset = 5 for thing in data: if isinstance(thing, types.IntType): b[offset] = thing offset = offset + 1 else: length = int((len(thing) + 4) / 4) b.padstring(thing, '\0', offset * 4, (offset + length) * 4) offset = offset + length break b[0] = offset * 4 # Wimp_SendMessage swi.swi(0x400e7, 'ibii', event, b, receiver, icon) def close_menus(): # Wimp_CreateMenu swi.swi(0x400d4, '.-') def set_colour(colour): swi.swi('ColourTrans_SetGCOL', 'i..00', colour << 8) def plot(type, x, y): # OS_Plot swi.swi(0x45, 'iii', type, x, y) def pointer(): bb = swi.block(20) # Wimp_GetPointerInfo swi.swi(0x400cf, '.b', bb) return bb[0], bb[1], bb[2], bb[3], bb[4] class window: "Wimp window class" def __init__(self, id): "Load window from template file" # Wimp_OpenTemplate try: swi.swi(0x400d9, '.s', directory + '.Resources.' + territory + '.Templates') except swi.error: swi.swi(0x400d9, '.s', directory + '.Resources.UK.Templates') # Wimp_LoadTemplate buffer_size, workspace_size, found = swi.swi(0x400db, '.0..-s0;.ii...i', id) if not found: raise RuntimeError, m['error.template'] % id buffer = swi.block(buffer_size / 4 + 1) workspace = swi.block(workspace_size / 4 + 1) swi.swi(0x400db, '.bbe-s0', buffer, workspace, workspace, id) # Wimp_CreateWindow self.handle = swi.swi(0x400c1, '.b;i', buffer) self.workspace = workspace self.name = id self.title_pointer = buffer[18] self.icons = buffer[21] self.is_open = 0 # Wimp_CloseTemplate swi.swi(0x400da, '') windows[self.handle] = self self.contents = [] def remove(self): del windows[self.handle] bb = swi.block(20) bb[0] = self.handle # Wimp_DeleteWindow swi.swi(0x400c3, '.b', bb) def do_redraw(self): # Wimp_RedrawWindow more = swi.swi(0x400c8, '.b;i', b) self.ox = b[1] - b[5] self.oy = b[4] - b[6] self.redraw_start() while more: self.gx0 = b[7] - self.ox self.gy0 = self.oy - b[10] self.gx1 = b[9] - self.ox self.gy1 = self.oy - b[8] self.redraw() # Wimp_GetRectangle more = swi.swi(0x400ca, '.b;i', b) self.redraw_stop() def redraw_start(self): "Get ready to redraw the window" def redraw(self): "Redraw a rectangle of the window" if self.contents: for content in self.contents: 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)): content.redraw(self.ox, self.oy) def redraw_stop(self): "Tidy up after redrawing the window" def open(self, minx = None, miny = None, maxx = None, maxy = None, scrollx = None, scrolly = None, behind = None): "Open the window" bb = swi.block(20) bb[0] = self.handle if minx is None: # Wimp_GetWindowState swi.swi(0x400cb, '.b', bb) else: bb[1] = minx bb[2] = miny bb[3] = maxx bb[4] = maxy bb[5] = scrollx bb[6] = scrolly bb[7] = behind # Wimp_OpenWindow swi.swi(0x400c5, '.b', bb) self.is_open = 1 def open_centred(self, behind = None): "Open the window centred in the screen" bb = swi.block(20) bb[0] = self.handle # Wimp_GetWindowState swi.swi(0x400cb, '.b', bb) screen_width, screen_height = screen_size() width = bb[3] - bb[1] height = bb[4] - bb[2] if behind is None: behind = bb[7] self.open((screen_width - width) / 2, (screen_height - height) / 2, (screen_width + width) / 2, (screen_height + height) / 2, bb[5], bb[6], behind) def open_full_size(self, behind = None): bb = swi.block(20) bb[0] = self.handle swi.swi('Wimp_GetWindowState', '.b', bb) bb[3] = 10000 bb[2] = -10000 if behind: bb[7] = behind swi.swi('Wimp_OpenWindow', '.b', bb) self.is_open = 1 def open_as_menu(self): "Open the window as a menu centred on the pointer" bb = swi.block(20) bb[0] = self.handle # Wimp_GetWindowState swi.swi(0x400cb, '.b', bb) width = bb[3] - bb[1] height = bb[4] - bb[2] # Wimp_GetPointerInfo swi.swi(0x400cf, '.b', bb) # Wimp_CreateMenu swi.swi(0x400d4, '.iii', self.handle, bb[0] - width / 2, bb[1] + height / 2) def close(self): "Close the window" bb = swi.block(20) bb[0] = self.handle # Wimp_CloseWindow swi.swi(0x400c6, '.b', bb) self.is_open = 0 def entering(self): "Pointer entering window" def leaving(self): "Pointer leaving window" def click(self, icon, buttons, x, y, sx, sy): "Mouse click in window" self.contents.reverse() if self.contents: for content in self.contents: if content.pos[0] <= x <= content.pos[2] and content.pos[1] <= y <= content.pos[3]: if content.click(buttons, x - content.pos[0], y - content.pos[1]): self.contents.reverse() return 1 self.contents.reverse() return 0 def help(self, icon): "Return interactive help message" if m.has_key('help.' + self.name + '.' + str(icon)): return m['help.' + self.name + '.' + str(icon)] elif m.has_key('help.' + self.name): return m['help.' + self.name] return None def key(self, icon, key): "Handle key presses" # Wimp_ProcessKey swi.swi(0x400dc, 'i', key) def icon_text(self, icon, text = None): "Read or write an icon's text contents" if icon >= self.icons: raise RuntimeError, 'Icon does not exist in icon_text()' bb = swi.block(20) bb[0] = self.handle bb[1] = icon # Wimp_GetIconState swi.swi(0x400ce, '.b', bb) indirected = bb[6] & 0x100 if text is None: if indirected: return mem_string(bb[7]) else: return mem_string(bb.start + 28) else: if not indirected: raise RuntimeError, 'Icon not indirected in icon_text()' mem_string(bb[7], text) # Wimp_SetIconState bb[2] = bb[3] = 0 swi.swi(0x400cd, '.b', bb) # Wimp_GetCaretPosition swi.swi(0x400d3, '.b', bb) if bb[0] == self.handle and bb[1] == icon: self.put_caret(icon) def icon_crement(self, icon, min, max, step = 1): "Increment or decrement a numerical icon" number = self.icon_text(icon) if number == '': number = (step < 0) * min + (step > 0) * max else: number = eval(number) if number == (step < 0) * min + (step > 0) * max: return if number > max: number = max elif number < min: number = min else: number = number + step self.icon_text(0, str(number)) def icon_set(self, icon, state = None): "Return the state or set the state of an icon" if icon >= self.icons: raise RuntimeError, 'Icon does not exist in icon_set()' bb = swi.block(20) bb[0] = self.handle bb[1] = icon # Wimp_GetIconState swi.swi(0x400ce, '.b', bb) if state is None: return (bb[6] & 0x200000) == 0x200000 else: bb[6] = bb[6] & ~0x200000 if state: bb[6] = bb[6] | 0x200000 # Wimp_SetIconState bb[2] = bb[3] = bb[6] swi.swi(0x400cd, '.b', bb) def put_caret(self, icon = -1, x = 0, y = 0, height = 0x2000000): "Place the caret in the window" if icon == -1: # Wimp_SetCaretPosition swi.swi(0x400d2, 'iiiii0', self.handle, icon, x, y, height) else: # Wimp_SetCaretPosition swi.swi(0x400d2, 'ii00-i', self.handle, icon, len(self.icon_text(icon))) def icon_grey(self, icon, grey = 1): "(Un)grey out an icon" if icon >= self.icons: raise RuntimeError, 'Icon does not exist in icon_grey()' bb = swi.block(20) bb[0] = self.handle bb[1] = icon bb[2] = 0x400000 * grey bb[3] = 0x400000 swi.swi(0x400cd, '.b', bb) def icon_ungrey(self, icon): "Ungrey an icon" self.icon_grey(icon, 0) def add_content(self, content): "Add a content class to the window" self.contents.append(content) def remove_content(self, content): "Remove a content class from the window" self.contents.remove(content) def refresh(self, x0, y0, x1, y1): "Force a redraw of an area of the window" swi.swi('Wimp_ForceRedraw', 'iiiii', self.handle, x0, -y1, x1, -y0) def update(self, x0, y0, x1, y1): "Update an area of the window" bb = swi.block(20) bb[0] = self.handle bb[1] = x0 bb[2] = -y1 bb[3] = x1 bb[4] = -y0 # Wimp_UpdateWindow more = swi.swi(0x400c9, '.b;i', bb) self.ox = bb[1] - bb[5] self.oy = bb[4] - bb[6] self.redraw_start() while more: self.gx0 = bb[7] - self.ox self.gy0 = self.oy - bb[10] self.gx1 = bb[9] - self.ox self.gy1 = self.oy - bb[8] self.redraw() # Wimp_GetRectangle more = swi.swi(0x400ca, '.b;i', bb) self.redraw_stop() def title(self, title = None): "Change or return the window title" if title is None: return mem_string(self.title_pointer, None) else: mem_string(self.title_pointer, title) if self.is_open: bb = swi.block(20) # Wimp_GetCaretPosition swi.swi(0x400d3, '.b', bb) if bb[0] == self.handle: # Wimp_SetCaretPosition swi.swi(0x400d2, '-000--') swi.swi(0x400d2, 'iiiiii', bb[0], bb[1], bb[2], bb[3], bb[4], bb[5]) else: # Wimp_SetCaretPosition swi.swi(0x400d2, 'i-00i-', self.handle, 1<<25) swi.swi(0x400d2, 'iiiiii', bb[0], bb[1], bb[2], bb[3], bb[4], bb[5]) def set_extent(self, x0, y0, x1, y1): "Set the work area extent in os units" bb = swi.block(20) bb[0] = x0 bb[1] = -y1 bb[2] = x1 bb[3] = -y0 # Wimp_SetExtent swi.swi(0x400d7, 'ib', self.handle, bb) def origin(self): "Return the window origin position on the screen" bb = swi.block(20) bb[0] = self.handle swi.swi('Wimp_GetWindowState', '.b', bb) self.ox = b[1] - b[5] self.oy = b[4] - b[6] return self.ox, self.oy def icon_box(self, icon): "Return the bbox of an icon relative to the window" bb = swi.block(20) bb[0] = self.handle bb[1] = icon swi.swi('Wimp_GetIconState', '.b', bb) return bb[2], -bb[5], bb[4], -bb[3] def icon_drag(self, icon): "Start to drag the specified icon" self.origin() x0, y0, x1, y1 = self.icon_box(icon) bb = swi.block(20) bb[0] = self.ox + x0 bb[1] = self.oy - y1 bb[2] = self.ox + x1 bb[3] = self.oy - y0 swi.swi('DragASprite_Start', 'i1sb', 0xc5, self.icon_text(icon), bb) class iconbar_icon(window): def __init__(self, sprite, handler, indirected = 0): "Create an iconbar icon with the specified sprite" bb = swi.block(20) bb[0] = -1 bb[1] = bb[2] = 0 bb[3] = bb[4] = 68 if indirected: bb[5] = 0x311a self.buffer = swi.block(5) self.buffer.padstring(sprite, '\0') bb[6] = self.buffer.start bb[7] = 1 bb[8] = len(sprite) else: bb[5] = 0x301a bb.padstring(sprite, '\0', 24, 36) # Wimp_CreateIcon self.icon = swi.swi(0x400c2, '0b;i', bb) self.icons = self.icon + 1 self.handle = -2 self.name = 'iconbar' self.handler = handler windows[-2] = self def click(self, icon, buttons, x, y, sx, sy): "Handle a click on the iconbar" self.handler(buttons, sx) def change(self, sprite): self.icon_text(self.icon, sprite) class info_window(window): "Info box window including email/web buttons" def __init__(self): "Load the window template" window.__init__(self, 'info') def click(self, icon, buttons, x, y, sx, sy): "Handle mouse clicks" try: if icon == 8 or icon == 9: if os.path.exists('System:Modules.Network.URI'): # Wimp_StartTask swi.swi(0x400de, 's', 'RMEnsure AcornURI 0.00 RMRun System:Modules.Network.URI') if icon == 8: # URI_Dispatch swi.swi(0x4e381, '0s0', m['email']) elif icon == 9: # URI_Dispatch swi.swi(0x4e381, '0s0', m['www']) except swi.error: swi.swi(0x107, '') class save_window(window): "Standard RISC OS save window" def __init__(self, handler, type): "Load the window template" window.__init__(self, 'save') self.handler = handler self.type = type def click(self, icon, buttons = 0, x = 0, y = 0, sx = 0, sy = 0): "Handle mouse clicks" if icon == 0 and buttons > 15: if self.icon_text(1) == '': warning(m['error.save.empty']) else: self.drag_save() elif icon == 2: close_menus() self.close() elif icon == 3: path = self.icon_text(1) if os.path.isabs(path): self.handler(path, 0) close_menus() self.close() else: warning(m['error.save.tosave']) def key(self, icon, key): "Handle key presses" if key == 0xd: self.click(3) elif key == 0x1b: self.click(2) else: # Wimp_ProcessKey swi.swi(0x400dc, 'i', key) def path(self, path): "Set the save path" self.icon_text(1, path) def drag_save(self): "Save the object by dragging the icon" self.icon_drag(0) while 1: event, details = poll() if event == 7: swi.swi('DragASprite_Stop', '') mouse = pointer() path = self.icon_text(1) # Message_DataSave send_message(18, 1, mouse[3], (mouse[3], mouse[4], mouse[0], mouse[1], 0, self.type, os.path.basename(path)), mouse[4]) break else: handle((event, details)) event, details = poll() if event == 19 and details == 1: # Message_DataSave bounced pass elif event in (17, 18) and details == 2: path = b.nullstring(44, b[0]) # Message_DataSaveAck if self.handler(path, b[9] == -1): # Message_DataLoad send_message(17, 3, b[1], (b[5], b[6], b[7], b[8], b[9], b[10], path), ref = b[2]) close_menus() self.close() else: handle((event, details)) class menu: "Wimp menu class" def __init__(self, name, items): "Load menu" self.blocks = [] self.menu_block = self._load(items) self.name = name def menu(self, x, y): "Display menu and return the selection" global current_menu current_menu = self swi.swi(0x400d4, '.bii', self.menu_block, x, y) while 1: event, details = poll(1) # Menu_Selection if event == 9: bb = swi.block(20) # Wimp_GetPointerInfo swi.swi(0x400cf, '.b', bb) return details, (bb[2] == 1) # User_Message, User_Message_Recorded: Message_MenusDeleted elif (event == 17 or event == 18) and details == 0x400c9: return None, 0 else: handle((event, details)) def popup(self, window, icon): "Popup the menu next to the icon" bb = swi.block(20) bb[0] = window.handle bb[1] = icon # Wimp_GetIconState swi.swi(0x400ce, '.b', bb) x = bb[4] y = bb[5] # Wimp_GetWindowState swi.swi(0x400cb, '.b', bb) return self.menu(bb[1] - bb[5] + x, bb[4] - bb[6] + y) def _load(self, items): "Create a wimp menu structure (recursively)" menu_block = swi.block(7 + 6 * (len(items) - 1)) self.blocks.append(menu_block) title_block = swi.block(len(lookup(items[0])) / 4 + 1) self.blocks.append(title_block) title_block.padstring(lookup(items[0]), '\0') menu_block[0] = title_block.start menu_block[1] = menu_block[2] = -1 menu_block[3] = 0x00070207 menu_block[4] = 300 menu_block[5] = 44 menu_block[6] = 0 offset = 7 for item in items[1:]: if isinstance(item, types.StringType): text = item submenu = -1 else: text = item[0] if isinstance(item[1], window): submenu = item[1].handle else: submenu = self._load(item[1]).start grey = False if text[0] == '-': grey = True text = text[1:] item_block = swi.block(len(lookup(text)) / 4 + 1) self.blocks.append(item_block) item_block.padstring(lookup(text), '\0') menu_block[offset] = (0x100 * (offset == 7)) menu_block[offset + 1] = submenu menu_block[offset + 2] = 0x07000111 if grey: menu_block[offset + 2] |= 1 << 22 menu_block[offset + 3] = item_block.start menu_block[offset + 4] = menu_block[offset + 5] = -1 offset = offset + 6 menu_block[offset - 6] = menu_block[offset - 6] | 0x80 return menu_block class content: "Base window content class" def __init__(self, w, x0, y0, x1, y1): "Initialise a window content" self.window = w self.pos = [x0, y0, x1, y1] def redraw(self, ox, oy): "Redraw content" pass def click(self, buttons, x, y): "Handle clicks in the content" return 0 def refresh(self): "Force redraw of the content" self.window.refresh(self.pos[0] - 4, self.pos[1] - 4, self.pos[2] + 4, self.pos[3] + 4) def update(self): "Update the content" self.window.update(self.pos[0] - 4, self.pos[1] - 4, self.pos[2] + 4, self.pos[3] + 4) class rectangle(content): "Example window content" def __init__(self, w, x0, y0, x1, y1, fill = None, border = None, text = None, font = None, text_colour = 0x000000): content.__init__(self, w, x0, y0, x1, y1) self.fill = fill self.border = border self.text = text self.font = font self.text_colour = text_colour def redraw(self, ox, oy): if not self.fill is None: # ColourTrans_SetGCOL swi.swi(0x40743, 'i..i0', self.fill << 8, 1 << 8) # OS_Plot swi.swi(0x45, '4ii', ox + self.pos[0], oy - self.pos[3]) swi.swi(0x45, 'iii', 96 + 5, ox + self.pos[2], oy - self.pos[1]) if not self.border is None: # ColourTrans_SetGCOL swi.swi(0x40743, 'i..i0', self.border << 8, 1 << 8) # OS_Plot swi.swi(0x45, '4ii', ox + self.pos[0], oy - self.pos[3]) swi.swi(0x45, '5ii', ox + self.pos[2], oy - self.pos[3]) swi.swi(0x45, '5ii', ox + self.pos[2], oy - self.pos[1]) swi.swi(0x45, '5ii', ox + self.pos[0], oy - self.pos[1]) swi.swi(0x45, '5ii', ox + self.pos[0], oy - self.pos[3]) if not self.text is None and not self.font is None: self.font.colour(0xdddddd, self.text_colour) self.font.paint(self.text, 400 * (ox + self.pos[0]), 400 * (oy - (self.pos[1] + self.pos[3] * 3) / 4)) def click(self, buttons, x, y): #self.window.remove_content(self) #self.refresh() return 0 class paragraph(content): "A paragraph of text" def __init__(self, w, x0, y0, x1, font_family, font_style, font_size, text, text_colour = 0x000000, fill = None, padding = 10): self.font_family = font_family self.font_style = font_style self.font_size = font_size self.line_height = int(font_size * 0.2) self.text = [] self.text_colour = text_colour self.fill = fill self.padding = padding text = text.encode('utf8') width = x1 - x0 - padding - padding while text: (c, x) = rufl.split(font_family, font_style, font_size, text, width) s = text[:c] space = s.rfind(' ') if c != len(text) and space != -1: c = space s = text[:c + 1] text = text[c + 1:] self.text.append(s) y1 = y0 + len(self.text) * self.line_height + padding + padding content.__init__(self, w, x0, y0, x1, y1) self.y1 = y1 def redraw(self, ox, oy): if not self.fill is None: swi.swi('ColourTrans_SetGCOL', 'i..i0', self.fill << 8, 1 << 8) swi.swi('OS_Plot', '4ii', ox + self.pos[0], oy - self.pos[3]) swi.swi('OS_Plot', 'iii', 96 + 5, ox + self.pos[2], oy - self.pos[1]) swi.swi('ColourTrans_SetFontColours', 'iiii', 0, 0xffffff << 8, self.text_colour << 8, 14) y = oy - self.pos[1] - self.padding - self.line_height * 0.75 for line in self.text: rufl.paint(self.font_family, self.font_style, self.font_size, line, ox + self.pos[0] + self.padding, y, rufl.blend) y -= self.line_height def font(name, size_x, size_y, no_encoding = False): f = (name, size_x, size_y) if fonts.has_key(f): font = fonts[f] font.use = font.use + 1 return font else: fonts[f] = _font(name, size_x, size_y, no_encoding) return fonts[f] class _font: def __init__(self, name, size_x, size_y, no_encoding): self.name = name self.size_x = size_x self.size_y = size_y self.use = 1 if no_encoding: self.handle = swi.swi('XFont_FindFont', '.sii00;i', self.name, self.size_x, self.size_y) self.utf8 = False else: try: self.handle = swi.swi('XFont_FindFont', '.sii00;i', self.name + '\EUTF8', self.size_x, self.size_y) self.utf8 = True except swi.error: self.handle = swi.swi('XFont_FindFont', '.sii00;i', self.name + '\ELatin1', self.size_x, self.size_y) self.utf8 = False def lose(self): self.use = self.use - 1 if self.use == 0: # Font_LoseFont swi.swi(0x40082, 'i', self.handle) def paint(self, string, x, y, spacex = 0, spacey = 0, offx = 0, offy = 0, x0 = 0, y0 = 0, x1 = 0, y1 = 0): if self.utf8: s = string.encode('utf8') else: s = string.encode('latin1', 'replace') if x0: bb = swi.block(20) bb[0] = spacex bb[1] = spacey bb[2] = offx bb[3] = offy bb[4] = int(x0) bb[5] = int(y0) bb[6] = int(x1) bb[7] = int(y1) swi.swi('Font_Paint', 'isiiib', self.handle, s, 0x322, x, y, bb) else: swi.swi('Font_Paint', 'isiii', self.handle, s, 0x300, x, y) def charbbox(self, char): return swi.swi('Font_CharBBox', 'ii0;.iiii', self.handle, ord(char)) def colour(self, back, fore): # ColourTrans_SetFontColours swi.swi(0x4074f, 'iiii', self.handle, back << 8, fore << 8, 14) def stringbox(self, string): if string == '': return (0, 0, 0, 0) if string[-1] == ' ': string = string + ' ' if self.utf8: s = string.encode('utf8') else: s = string.encode('latin1', 'replace') bb = swi.block(20) bb[0] = bb[1] = bb[2] = bb[3] = 0 bb[4] = -1 # Font_ScanString swi.swi(0x400a1, 'isiiib', self.handle, s, 0x40320, 0x7fffffff, 0x7fffffff, bb) return bb[5], bb[6], bb[7], bb[8] def caretpos(self, string, x, y): if self.utf8: s = string.encode('utf8') else: s = string.encode('latin1', 'replace') bb = swi.block(len(s) / 4 + 10) bb.padstring(s, chr(0)) # Font_ScanString pos, cx, cy = swi.swi(0x400a1, 'ibiii;.i.ii', self.handle, bb, 0x20300, x, y) return pos - bb.start, cx, cy def caretxy(self, string, pos): if self.utf8: s = string.encode('utf8') else: s = string.encode('latin1', 'replace') # Font_ScanString return swi.swi(0x400a1, 'isiii..i;...ii', self.handle, s, 0x20380, 0x7fffffff, 0x7fffffff, pos) def fontbox(self): return swi.swi('Font_ReadInfo', 'i;.iiii', self.handle) def split(self, string, x, y): if self.utf8: s = string.encode('utf8') else: s = string.encode('latin1', 'replace') bb = swi.block(len(s) / 4 + 10) bb.padstring(s, chr(0)) # Font_ScanString pos = swi.swi(0x400a1, 'ibiii;.i', self.handle, bb, 0x300, x, y) return pos - bb.start class document: def __init__(self, width, height, margin): self.contents = [] self.views = [] self.extent = [width, height] self.margin = margin self.fonts = {} self.fonts_use = {} def add_content(self, content): self.contents.append(content) for view in self.views: content.add_view(view) def remove_content(self, content): self.contents.remove(content) def add_view(self, view): self.views.append(view) for content in self.contents: content.add_view(view) def remove_view(self, view): self.views.remove(view) for content in self.contents: content.remove_view(view) def rescaled(self, view): for content in self.contents: content.remove_view(view) content.add_view(view) def redraw_start(self, view, scale): pass def redraw(self, view, scale): if self.margin: self.redraw_margin(view) for content in self.contents: content.scaled_pos = map(lambda z, scale = scale: scale * z / 400, content.pos) 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)): content.redraw_box = [view.gx0 * 400 / scale - content.pos[0], view.gy0 * 400 / scale - content.pos[1], view.gx1 * 400 / scale - content.pos[0], view.gy1 * 400 / scale - content.pos[1]] content.redraw(view, view.ox, view.oy, scale) def redraw_stop(self, view, scale): pass def redraw_margin(self, view): set_colour(0x777777) plot(4, view.ox + view.gx0, view.oy) plot(97, view.gx1 - view.gx0, self.margin) plot(4, view.ox + view.gx0, view.oy - view.extent[1]) plot(97, view.gx1 - view.gx0, -self.margin) plot(4, view.ox, view.oy - view.gy1) plot(97, -self.margin, view.gy1 - view.gy0) plot(4, view.ox + view.extent[0], view.oy - view.gy1) plot(97, self.margin, view.gy1 - view.gy0) def click(self, view, buttons, x, y): for content in self.contents: if content.pos[0] <= x <= content.pos[2] and content.pos[1] <= y <= content.pos[3]: content.click(view, buttons, x - content.pos[0], y - content.pos[1]) def refresh(self, x0, y0, x1, y1): for view in self.views: view.update(int(view.scale * x0 / 400 - 4), int(view.scale * y0 / 400 - 4), int(view.scale * x1 / 400 + 4), int(view.scale * y1 / 400 + 4)) def add_font(self, name, size_x, size_y): f = (name, size_x, size_y) if self.fonts.has_key(f): self.fonts_use[f] = self.fonts_use[f] + 1 else: self.fonts[f] = font(name, size_x, size_y) self.fonts_use[f] = 1 def remove_font(self, name, size_x, size_y): f = (name, size_x, size_y) self.fonts_use[f] = self.fonts_use[f] - 1 if self.fonts_use[f] == 0: self.fonts[f].lose() del self.fonts[f] del self.fonts_use[f] class document_view(window): def __init__(self, document, id, scale): window.__init__(self, id) self.document = document self.fonts = {} self.rescale(scale) document.add_view(self) self.caret = None def redraw_start(self): self.document.redraw_start(self, self.scale) def redraw(self): self.document.redraw(self, self.scale) def redraw_stop(self): self.document.redraw_stop(self, self.scale) def click(self, icon, buttons, x, y, sx, sy): if 0 <= x <= self.extent[0] and 0 <= y <= self.extent[1]: self.document.click(self, buttons, 400 * x / self.scale, 400 * y / self.scale) def key(self, icon, key): if self.caret: self.caret[0].key(self, key) else: swi.swi(0x400dc, 'i', key) def rescale(self, scale): for f in self.fonts.values(): f.lose() self.fonts = {} for f in self.document.fonts.keys(): self.fonts[f] = font(f[0], f[1] * scale, f[2] * scale) self.scale = scale self.extent = (int(scale * self.document.extent[0] / 400), int(scale * self.document.extent[1] / 400)) self.set_extent(-self.document.margin, -self.document.margin, self.extent[0] + self.document.margin, self.extent[1] + self.document.margin) self.document.rescaled(self) class document_content: def __init__(self, document, x0, y0, x1, y1): self.pos = [x0, y0, x1, y1] self.colour = 0x0000ff self.document = document document.add_content(self) def add_view(self, view): pass def remove_view(self, view): pass def redraw(self, view, x, y, scale): set_colour(self.colour) plot(4, x + self.scaled_pos[0], y - self.scaled_pos[1]) plot(21, x + self.scaled_pos[2], y - self.scaled_pos[1]) plot(21, x + self.scaled_pos[2], y - self.scaled_pos[3]) plot(21, x + self.scaled_pos[0], y - self.scaled_pos[3]) plot(21, x + self.scaled_pos[0], y - self.scaled_pos[1]) plot(21, x + self.scaled_pos[2], y - self.scaled_pos[3]) plot(4, x + self.scaled_pos[2], y - self.scaled_pos[1]) plot(21, x + self.scaled_pos[0], y - self.scaled_pos[3]) def click(self, view, buttons, x, y): self.colour = 0xffffff - self.colour self.document.refresh(self.pos[0], self.pos[1], self.pos[2], self.pos[3])