[pypy-svn] r49688 - in pypy/branch/newdotviewer: . test

misto at codespeak.net misto at codespeak.net
Wed Dec 12 23:07:04 CET 2007


Author: misto
Date: Wed Dec 12 23:07:04 2007
New Revision: 49688

Added:
   pypy/branch/newdotviewer/display.py
   pypy/branch/newdotviewer/drivers.py
   pypy/branch/newdotviewer/protocol.py
   pypy/branch/newdotviewer/render.py
   pypy/branch/newdotviewer/test/test_graphclient.py
   pypy/branch/newdotviewer/test/test_graphparse.py
   pypy/branch/newdotviewer/test/test_service.py
Modified:
   pypy/branch/newdotviewer/test/test_translator.py
Log:
added test files and temp refactoring modules

Added: pypy/branch/newdotviewer/display.py
==============================================================================
--- (empty file)
+++ pypy/branch/newdotviewer/display.py	Wed Dec 12 23:07:04 2007
@@ -0,0 +1,203 @@
+from graphdisplay import GraphDisplay
+from drivers import PyGameDriver
+from render import MyGraphRenderer 
+
+import pygame # TODO BAD BAD
+
+
+class View:
+    SCALEMAX = 100
+    SCALEMIN = 1
+    
+    def __init__ (self, display):
+        self.display = display
+        self.margin = 0.6
+        self.setscale (75)
+        self.setoffset (0, 0)
+        
+    def calculate_zoom_to_fit (self, view):
+        width, height = self.display.size
+        
+        return min(float(width) / view.width,
+                float(height) / view.height,
+                float(view.SCALEMAX) / view.scale)
+        
+    def setoffset (self, offsetx, offsety):
+        "Set the (x,y) origin of the rectangle where the graph will be rendered."
+        self.ofsx = offsetx 
+        self.ofsy = offsety 
+   
+    #ofsx = property (lambda self, off: off - self.margin )
+    #ofsy = property (lambda self, off: off - self.margin )
+   
+    def setscale (self, scale):
+        scale = max(min(scale, self.SCALEMAX), self.SCALEMIN)
+        self.scale = float(scale)
+        self.bboxh = self.display.size[1]
+    
+    #margin = property (lambda self: int(self.MARGIN * self.scale))
+    width = property (lambda self: int(self.display.size[0] * self.scale) + (2 * self.margin))
+    height = property (lambda self: int(self.display.size[1] * self.scale) + (2 * self.margin))
+    
+    def map (self, x, y):
+        ""
+        mx = (x * self.scale) + 2*self.margin*self.scale
+        my =  (self.display.size[1] - y* self.scale) - 2 * self.margin*self.scale 
+        return int(mx), int(my)
+                        
+    def shiftoffset(self, dx, dy):
+        self.ofsx += dx
+        self.ofsy += dy
+        
+    def reoffset(self, swidth, sheight):
+        offsetx = noffsetx = self.ofsx
+        offsety = noffsety = self.ofsy
+        width = self.width
+        height = self.height
+
+        # if it fits, center it, otherwise clamp
+        if width <= swidth:
+            noffsetx = (width - swidth) // 2
+        else:
+            noffsetx = min(max(0, offsetx), width - swidth)
+
+        if height <= sheight:
+            noffsety = (height - sheight) // 2
+        else:
+            noffsety = min(max(0, offsety), height - sheight)
+
+        self.ofsx = noffsetx
+        self.ofsy = noffsety
+                        
+    def shiftscale (self, factor, fix=None):
+        if fix is None:
+            fixx, fixy = self.display.size
+            fixx //= 2
+            fixy //= 2
+        else:
+            fixx, fixy = fix
+        x, y = self.revmap(fixx, fixy)
+        w, h = self.display.size
+        
+        self.setscale(self.scale * factor, w, h)
+        newx, newy = self.map(x, y)
+        self.shiftoffset(newx - fixx, newy - fixy)
+        
+    def calculate_scale_to_fit (self, (width,height)):
+        maxw, maxh = self.display.size
+        scale = min(float(maxw) / width,
+                    float(maxh) / height)
+        self.setscale (scale)
+    
+    def revmap(self, px, py):
+        return ((px + (self.ofsx - self.margin)) / self.scale,
+                self.bboxh - (py + (self.ofsy - self.margin)) / self.scale)
+        
+    def getboundingbox(self):
+        "Get the rectangle where the graph will be rendered."
+        return (-self.ofsx, -self.ofsy, self.width, self.height)
+ 
+    def visible(self, x1, y1, x2, y2):
+        """Is any part of the box visible (i.e. within the bounding box)?
+
+        We have to perform clipping ourselves because with big graphs the
+        coordinates may sometimes become longs and cause OverflowErrors
+        within pygame.
+        """
+        return x1 < self.width and x2 > 0 and y1 < self.height and y2 > 0
+
+    def visible_node (self, x, y, w, h):
+        
+        
+        x, y = self.map (x, y)
+        nw2 = int(w * self.scale)//2
+        nh2 = int(h * self.scale)//2
+        
+        return True
+        
+        return x-nw2 < w and x+nw2 > 0 and y-nh2 < h and y+nh2 > 0
+    
+    def visible_edge (self, x1, y1, x2, y2):
+        return True
+        w, h = self.width, self.height
+        x1, y1 = self.map(x1, y1)
+        x2, y2 = self.map(x2, y2)
+        return x1 < w and y1 < h and x2 > 0 and y2 > 0
+
+class MyDisplay (GraphDisplay):
+   
+    def __init__(self, size):
+        self.driver = PyGameDriver()
+        self.viewers_history = []
+        self.forward_viewers_history = []
+        self.highlight_word = None
+        self.highlight_obj = None
+        self.viewer = None
+        self.method_cache = {}
+        self.key_cache = {}
+        self.ascii_key_cache = {}
+        self.status_bar_height = 0
+        self.searchstr = None
+        self.searchpos = 0
+        self.searchresults = []
+        self.size = size
+        self.driver.resize (size)
+        self.initialize_keys()
+        self.font = pygame.font.Font (self.STATUSBARFONT, 16)
+   
+    def updated_viewer(self, view,keep_highlight=False):
+        width , height = self.size
+        view.reoffset(width, height)
+        if not keep_highlight:
+            self.sethighlight()
+            self.update_status_bar()
+        self.must_redraw = True
+    
+    def redraw_now(self):
+        self.status_bar_height = 0
+        pygame.display.flip()
+        self.must_redraw = False
+    
+    def display_graph (self, graph):
+        rend = MyGraphRenderer (self.driver)
+        view = View (self)
+        view.calculate_scale_to_fit(graph.boundingbox)
+        rend.render (graph, view)
+        self.redraw_now ()
+
+    def process_event (self, event):
+        graph = event.get ('layout', None)
+        if graph:
+            self.display_graph (graph)
+            self.driver.update()
+            
+    def controller_process_event(self, event):
+        method = self.method_cache.get(event.type, KeyError)
+        if method is KeyError:
+            method = getattr(self, 'process_%s' % (pygame.event.event_name(event.type),), None)
+            self.method_cache[method] = method
+        if method is not None:
+            method(event)
+            
+    def notifymousepos(self, pos):
+        pass
+    
+    def notifyclick (self, pos):
+        pass
+    
+    def zoom (self, amount):
+        pass
+    
+    def loop (self):
+        self.dragging = self.click_origin = self.click_time = None
+        try:
+            EventQueue = []
+            while True:
+                if not EventQueue:
+                    EventQueue.append(pygame.event.wait())
+                    EventQueue.extend(pygame.event.get())
+
+                self.controller_process_event(EventQueue.pop(0))
+
+        except StopIteration:
+            pass
\ No newline at end of file

Added: pypy/branch/newdotviewer/drivers.py
==============================================================================
--- (empty file)
+++ pypy/branch/newdotviewer/drivers.py	Wed Dec 12 23:07:04 2007
@@ -0,0 +1,132 @@
+import pygame
+    
+COLOR = {
+    'black': (0,0,0),
+    'white': (255,255,255),
+    'red': (255,0,0),
+    'green': (0,255,0),
+    'blue': (0,0,255),
+    'yellow': (255,255,0),
+    }
+import os
+this_dir = os.path.dirname(os.path.abspath(__file__))
+FONT = os.path.join(this_dir, 'cyrvetic.ttf')
+FIXEDFONT = os.path.join(this_dir, 'VeraMoBd.ttf')
+
+
+# TODO 
+class FakeImg:
+    fakeimg = True
+    def get_size(self):
+        return (0 , 0)
+    
+class FakeFont:
+    fakefont = True
+    def render (self, *args):
+        return FakeImg()
+   
+class PyGameDriver:
+    
+    def __init__ (self):
+        self.FONTCACHE = {}
+        self.size = 0,0 
+        
+    def resize(self, (w,h)):
+        self.width = w
+        self.height = h
+        self.size = w,h 
+        pygame.display.init()
+        pygame.font.init()
+        self.screen = pygame.display.set_mode((w, h), pygame.HWSURFACE|pygame.RESIZABLE, 32)
+    
+    def combine(self, color1, color2, alpha):
+        r1, g1, b1 = color1
+        r2, g2, b2 = color2
+        beta = 1.0 - alpha
+        return (int(r1 * alpha + r2 * beta),
+                int(g1 * alpha + g2 * beta),
+                int(b1 * alpha + b2 * beta))
+    
+    def highlight_color (self,color):
+        if color == (0, 0, 0): # black becomes magenta
+            return (255, 0, 255)
+        elif color == (255, 255, 255): # white becomes yellow
+            return (255, 255, 0)
+        intensity = sum(color)
+        if intensity > 191 * 3:
+            return self.combine(color, (128, 192, 0), 0.2)
+        else:
+            return self.combine(color, (255, 255, 0), 0.2)
+    
+    def parse_webcolor (self, name):
+        return (int(name[1:3],16), int(name[3:5],16), int(name[5:7],16))
+    
+    def getcolor (self, name, default):
+        if name in COLOR:
+            return COLOR[name]
+        elif name.startswith('#') and len(name) == 7:
+            rval = COLOR[name] = self.parse_webcolor (name)
+            return rval
+        else:
+            return default
+    
+    def get_canvas_size (self):
+        return self.width, self.height
+            
+    def process_event (self, event, display):
+        def display_async_quit():
+            pygame.event.post(pygame.event.Event(QUIT))        
+
+        def display_async_cmd(**kwds):                
+            pygame.event.post(pygame.event.Event(USEREVENT, **kwds))
+            
+    def getfont (self, size, fixedfont=False):
+        selected = None
+        if size in self.FONTCACHE:
+            selected = self.FONTCACHE[size]
+        elif size < 5:
+            self.FONTCACHE[size] = None
+        else:
+            if fixedfont:
+                filename = FIXEDFONT
+            else:
+                filename = FONT
+            selected = self.FONTCACHE[size] = pygame.font.Font(filename, size)
+        return selected or FakeFont()
+    
+    def fill (self, *args):
+        self.screen.fill(*args)
+    
+    def draw_lines (self, *args):
+        pygame.draw.lines (self.screen, *args)
+        
+    def draw_rect (self, *args):
+        pygame.draw.rect(self.screen, *args)
+        
+    def draw_polygon (self, *args):
+        pygame.draw.polygon (self.screen, *args)
+
+    def update (self):
+        pygame.display.flip()
+        
+    def draw_ellipse(self, *args):
+        pygame.draw.ellipse(self.screen, *args)
+        
+    def blit_image (self, *args):
+       img = args[0]
+       if getattr(img,'fakeimg', False):
+           return 
+       self.screen.blit (*args)
+       
+    def render_font (self, font, *args):
+        img = None
+        try:
+            try:
+                img = font.render (*args)
+            except pygame.error, e:
+                # Try *with* anti-aliasing to work around a bug in SDL
+                img = font.render (*args)
+        except pygame.error:
+            del parts[i]   # Text has zero width
+        return img
+    

Added: pypy/branch/newdotviewer/protocol.py
==============================================================================
--- (empty file)
+++ pypy/branch/newdotviewer/protocol.py	Wed Dec 12 23:07:04 2007
@@ -0,0 +1,169 @@
+import msgstruct
+import sys
+
+class FakeMessageTransport:
+    def __init__ (self):
+        self.source=None
+        self.sentmessages = []
+        self.closed = False
+        
+    def recvmsg (self):
+        while self.source:
+            return self.source.pop (0)
+        raise EOFError()
+    
+    def sendmsg (self, *args):
+        self.sentmessages.append (args)
+        
+    def close (self):
+        self.closed = True
+
+class Session:
+    def __init__ (self, transport=FakeMessageTransport()):
+        self.history = []
+        self.pagecache = {}
+        self.save_tmp_file = False
+        self.protocol = MessageProtocol(transport) 
+        
+    def show (self, page):
+        self.history.append (page)
+        self.reload (page)
+       
+    def getpage (self, graph_id):
+        page = self.history[graph_id]
+        result = None
+        try:
+            result = self.pagecache[page]
+        except KeyError:
+            result = page.content()
+            self.pagecache.clear()    # a cache of a single entry should be enough
+            self.pagecache[page] = result
+        return result
+
+    def reload (self,page):
+        if self.save_tmp_file:
+            f = open(save_tmp_file, 'w')
+            f.write(page.source)
+            f.close()
+        self.protocol.send_page (page)
+    
+    
+    def send_error(self, e):
+        try:
+            errmsg = str(e)
+            if errmsg:
+                errmsg = '%s: %s' % (e.__class__.__name__, errmsg)
+            else:
+                errmsg = '%s' % (e.__class__.__name__,)
+            self.transport.sendmsg(msgstruct.CMSG_SAY, errmsg)
+        except Exception:
+            pass
+        
+#from dotviewer import msgstruct
+class MessageProtocol:
+    INIT = []#(msgstruct.CMSG_INIT, msgstruct.MAGIC)]
+    
+    def __init__ (self, transport):
+        self.transport = transport
+        self.send_messages = self.first_message #dynamic state pattern 
+        
+    def first_message (self, message):
+        self._send_messages ((MessageProtocol.INIT))
+        self.send_messages = self._send_messages
+        
+    def _send_messages (self, messages):
+        ioerror = None
+        for msg in messages:
+            try:
+                self.transport.sendmsg (*msg)
+            except IOError, ioerror:
+                print >>sys.stderr, "Error: sending ", msg , str(ioerror)
+                break
+        return #XXX
+        # wait for MSG_OK or MSG_ERROR
+        try:
+            while True:
+                msg = self.transport.recvmsg()
+                if msg[0] == msgstruct.MSG_OK:
+                    break
+        except EOFError:
+            ioerror = IOError("connexion unexpectedly closed "
+                                         "(graphserver crash?)")
+        if ioerror is not None:
+            raise ioerror
+    
+    def send_page (self, page):
+        import graphparse
+        messages = graphparse.parse_dot(0, page.content(), page.links,getattr(page, 'fixedfont', False))
+        self.send_messages (messages)
+        
+class MessageDrivenPainter:
+    
+    def __init__ (self,service):
+        self.service = service
+        
+    def process_all (self, messages):
+        try:
+            for msg in messages:
+                self.process_message (msg)
+        except EOFError:
+            from drawgraph import display_async_quit
+            display_async_quit()
+            
+    def generate_layout (self, messages):
+        try:
+            for msg in messages:
+                self.process_message (msg)
+        except EOFError, errmsg:
+            print "Cannot process ", msg
+        return self.newlayout
+
+    def process_message (self, msg):
+        fn = self.MESSAGES.get(msg[0])
+        if fn:
+            fn(self, *msg[1:])
+        else:
+            self.log("unknown message code %r" % (msg[0],))
+
+    def log(self, info):
+        print >> sys.stderr, info
+
+    def cmsg_start_graph(self, graph_id, scale, width, height, *rest):
+        from drawgraph import GraphLayout
+        self.newlayout = GraphLayout(float(scale), float(width), float(height))
+            
+    def cmsg_add_node(self, *args):
+        self.newlayout.add_node(*args)
+
+    def cmsg_add_edge(self, *args):
+        self.newlayout.add_edge(*args)
+
+    def cmsg_add_link(self, word, *info):
+        if len(info) == 1:
+            info = info[0]
+        elif len(info) >= 4:
+            info = (info[0], info[1:4])
+        self.newlayout.links[word] = info
+
+    def cmsg_fixed_font(self, *rest):
+        self.newlayout.fixedfont = True
+
+    def cmsg_stop_graph(self, *rest):
+        self.service.post_event (layout=self.newlayout)
+    
+    def cmsg_missing_link(self, *rest):
+        self.setlayout(None)
+
+    def cmsg_say(self, errmsg, *rest):
+        self.service.post_event (say=errmsg)
+
+    MESSAGES = {
+        msgstruct.CMSG_START_GRAPH: cmsg_start_graph,
+        msgstruct.CMSG_ADD_NODE:    cmsg_add_node,
+        msgstruct.CMSG_ADD_EDGE:    cmsg_add_edge,
+        msgstruct.CMSG_ADD_LINK:    cmsg_add_link,
+        msgstruct.CMSG_FIXED_FONT:  cmsg_fixed_font,
+        msgstruct.CMSG_STOP_GRAPH:  cmsg_stop_graph,
+        msgstruct.CMSG_MISSING_LINK:cmsg_missing_link,
+        msgstruct.CMSG_SAY:         cmsg_say,
+        }
\ No newline at end of file

Added: pypy/branch/newdotviewer/render.py
==============================================================================
--- (empty file)
+++ pypy/branch/newdotviewer/render.py	Wed Dec 12 23:07:04 2007
@@ -0,0 +1,346 @@
+import drawgraph
+
+  
+
+class BoundingBox: #TODO this should be view ...
+    def __init__(self, node, xcenter, ycenter, view):
+        
+        self.xcenter, self.ycenter = xcenter,ycenter
+        self.boxwidth = int(node.w * view.scale) #TODO
+        self.boxheight = int(node.h * view.scale)
+        
+        self.wmax = self.boxwidth
+        self.hmax = self.boxheight
+        
+    def finalize (self):
+        self.xleft = self.xcenter - self.wmax//2
+        self.xright = self.xcenter + self.wmax//2
+        self.ytop = self.ycenter - self.hmax//2
+        self.x = self.xcenter-self.boxwidth//2
+        self.y = self.ycenter-self.boxheight//2
+        
+class Graph:
+    def __init__(self):
+        self.fixedfont = True
+        self.nodes = []
+        self.edges = []
+        
+    def __equals__ (self, other):
+        #fixedfont TODO
+        return other.nodes == self.nodes and other.edges == self.edges
+    
+class MyGraphRenderer (drawgraph.GraphRenderer):
+    def __init__ (self, driver,  scale=75):
+        self.driver = driver
+        self.textzones = []
+        self.highlightwords = []
+    
+    def setscale(self, scale, graph):
+        scale = max(min(scale, self.SCALEMAX), self.SCALEMIN)
+        self.scale = float(scale)
+        w, h = graph.boundingbox
+        self.margin = int(self.MARGIN * scale)
+        self.width = int(w * scale) + (2 * self.margin)
+        self.height = int(h * scale) + (2 * self.margin)
+        self.bboxh = h
+        size = int(15 * (scale-10) / 75)
+        self.font = self.driver.getfont (size, graph)
+        
+    def computevisible (self, graph, view):
+        visible = Graph()
+
+        for node in graph.nodes.values():
+            if view.visible_node (node.x,node.y, node.w, node.h):
+                visible.nodes.append(node)
+
+        for edge in graph.edges:
+            x1, y1, x2, y2 = edge.limits()            
+            if view.visible_edge (x1, y1, x2, y2):
+                visible.edges.append (edge)
+        return visible
+    
+    def draw_background (self, view):
+        bbox = view.getboundingbox()
+        
+        ox, oy, width, height = bbox
+        dpy_width, dpy_height = self.driver.size
+        # some versions of the SDL misinterpret widely out-of-range values,
+        # so clamp them
+        if ox < 0:
+            width += ox
+            ox = 0
+        if oy < 0:
+            height += oy
+            oy = 0
+        if width > dpy_width:
+            width = dpy_width
+        if height > dpy_height:
+            height = dpy_height
+            
+        self.driver.fill((224, 255, 224), (ox, oy, width, height))
+
+        # gray off-bkgnd areas
+        gray = (128, 128, 128)
+        if ox > 0:
+            self.driver.fill(gray, (0, 0, ox, dpy_height))
+        if oy > 0:
+            self.driver.fill(gray, (0, 0, dpy_width, oy))
+        w = dpy_width - (ox + width)
+        if w > 0:
+            self.driver.fill(gray, (dpy_width-w, 0, w, dpy_height))
+        h = dpy_height - (oy + height)
+        if h > 0:
+            self.driver.fill(gray, (0, dpy_height-h, dpy_width, h))
+        
+
+    def render (self, graph, view):
+        
+        # self.driver.setscale (scale,graph) the view has the scale TODO
+        # self.driver.setoffset (0,0) the view knows the offset
+        
+        visible = self.computevisible (graph, view)
+        self.draw_background (view)
+ 
+        # draw the graph and record the position of texts
+        del self.textzones[:]
+        for cmd in self.draw_commands (visible, view):
+            cmd()
+            
+    def draw_image (self, img, bbox):
+        w, h = img.get_size()
+        img.draw (bbox.xcenter-w//2, bbox.ytop+bbox.y) #XXXX WTF ???
+            
+    def if_font_none (self, commands, lines, graph, bgcolor, bbox):
+        if lines:
+            raw_line = lines[0].replace('\\l','').replace('\r','')
+            if raw_line:
+                for size in (12, 10, 8, 6, 4):
+                    font = self.driver.getfont (size, graph.fixedfont)
+                    img = MyTextSnippet(self.driver, self, font, raw_line, (0, 0, 0), bgcolor)
+                    w, h = img.get_size()
+                    if (w >= bbox.boxwidth or h >= bbox.boxheight):
+                        continue
+                    else:
+                        if w > bbox.wmax: bbox.wmax = w
+                        def cmd(bbox, img=img):
+                            self.draw_image (img, bbox)
+                        commands.append (cmd)
+                        bbox.hmax += h
+                        break
+        
+    def draw_node_commands (self, node, graph, bbox, view):
+        xcenter, ycenter = view.map(node.x, node.y)
+        
+        boxwidth = int(node.w * view.scale) #TODO
+        boxheight = int(node.h * view.scale)
+        
+        fgcolor = self.driver.getcolor(node.color, (0,0,0))
+        bgcolor = self.driver.getcolor(node.fillcolor, (255,255,255))
+        if node.highlight:
+            fgcolor = self.driver.highlight_color(fgcolor)
+            bgcolor = self.driver.highlight_color(bgcolor)
+
+        text = node.label
+        lines = text.replace('\\l','\\l\n').replace('\r','\r\n').split('\n')
+        # ignore a final newline
+        if not lines[-1]:
+            del lines[-1]
+
+        commands = []
+        bkgndcommands = []
+        
+        self.font = None
+        if self.font is None:
+            self.if_font_none(commands, lines, graph, bgcolor, bbox)
+        else:
+            for line in lines:
+                raw_line = line.replace('\\l','').replace('\r','') or ' '
+                img = MyTextSnippet(self, raw_line, (0, 0, 0), bgcolor)
+                w, h = img.get_size()
+                if w > bbox.wmax: bbox.wmax = w
+                if raw_line.strip():
+                    if line.endswith('\\l'):
+                        def cmd(bbox,img=img):
+                            img.draw(view.xleft, view.ytop+view.y)
+                    elif line.endswith('\r'):
+                        def cmd(bbox, img=img):
+                            img.draw(view.xright-view.w, view.ytop+view.y)
+                    else:
+                        def cmd(bbox, img=img):
+                            img.draw(view.xcenter-view.w//2, view.ytop+y)
+                    commands.append(cmd)
+                bbox.hmax += h
+                #hmax += 8
+
+        # we know the bounding box only now; setting these variables will
+        # have an effect on the values seen inside the cmd() functions above
+        
+        self.draw_node_shape (node, bkgndcommands, commands, bbox, boxheight, boxwidth, bgcolor, fgcolor)
+        return bkgndcommands, commands
+    
+     
+    def draw_node_shape (self, node, bkgndcommands, commands, view, boxheight, boxwidth, bgcolor, fgcolor):
+        if node.shape == 'box':
+            def cmd (view):
+                rect = (view.x-1, view.y-1, boxwidth+2, boxheight+2)
+                self.driver.fill(bgcolor, rect)
+            bkgndcommands.append (cmd)
+            def cmd(view):
+                #pygame.draw.rect(self.screen, fgcolor, rect, 1)
+                rect = (view.x-1, view.y-1, boxwidth+2, boxheight+2)
+                self.driver.draw_rect (fgcolor, rect, 1)
+            commands.append(cmd)
+        elif node.shape == 'ellipse':
+            def cmd(view):
+                rect = (view.x-1, view.y-1, boxwidth+2, boxheight+2)
+                self.driver.draw_ellipse(bgcolor, rect, 0)
+            bkgndcommands.append(cmd)
+            def cmd(view):
+                rect = (view.x-1, view.y-1, boxwidth+2, boxheight+2)
+                self.driver.draw_ellipse(fgcolor, rect, 1)
+            commands.append(cmd)
+        elif node.shape == 'octagon':
+            import math
+            def cmd(view):
+                step = 1-math.sqrt(2)/2
+                points = [(int(view.x+boxwidth*fx), int(view.y+boxheight*fy))
+                      for fx, fy in [(step,0), (1-step,0),
+                                     (1,step), (1,1-step),
+                                     (1-step,1), (step,1),
+                                     (0,1-step), (0,step)]]
+                self.driver.draw_polygon(bgcolor, points, 0)
+            bkgndcommands.append(cmd)
+            def cmd(view):
+                step = 1-math.sqrt(2)/2
+                points = [(int(view.x+boxwidth*fx), int(view.y+boxheight*fy))
+                      for fx, fy in [(step,0), (1-step,0),
+                                     (1,step), (1,1-step),
+                                     (1-step,1), (step,1),
+                                     (0,1-step), (0,step)]]
+                self.driver.draw_polygon(fgcolor, points, 1)
+            commands.append(cmd)
+            
+       
+    
+    def draw_edge_body (self, points, fgcolor):
+        #pygame.draw.lines(self.screen, fgcolor, False, points)
+        self.driver.draw_lines (fgcolor, False, points)
+    
+    def draw_edge (self, edge, edgebodycmd, edgeheadcmd, view):
+        fgcolor = self.driver.getcolor(edge.color, (0,0,0))
+        if edge.highlight:
+            fgcolor = self.driver.highlight_color(fgcolor)
+        points = [view.map(*xy) for xy in edge.bezierpoints()]
+        
+        edgebodycmd.append (Command (self.draw_edge_body, points, fgcolor))
+
+        points = [view.map(*xy) for xy in edge.arrowhead()]
+        if points:
+            def drawedgehead(points=points, fgcolor=fgcolor):
+                self.driver.draw_polygon(fgcolor, points, 0)
+            edgeheadcmd.append(drawedgehead)
+
+        if edge.label:
+            x, y = view.map(edge.xl, edge.yl)
+            font = self.driver.getfont (10) #TODO style
+            img = MyTextSnippet (self.driver, self, font, edge.label, (0, 0, 0))
+            w, h = img.get_size()
+            if view.visible(x-w//2, y-h//2, x+w//2, y+h//2):
+                def drawedgelabel(img=img, x1=x-w//2, y1=y-h//2):
+                    img.draw(x1, y1)
+                edgeheadcmd.append(drawedgelabel)
+        
+    def draw_commands(self, graph, view):
+        nodebkgndcmd = []
+        nodecmd = []
+        
+        for node in graph.nodes:
+            xcenter,ycenter = view.map (node.x, node.y)
+            bbox = BoundingBox (node, xcenter,ycenter, view)
+         
+            cmd1, cmd2 = self.draw_node_commands(node, graph, bbox,view)
+            bbox.finalize()
+            nodebkgndcmd += [ Command (c, bbox) for c in cmd1 ]
+            nodecmd += [ Command (c, bbox) for c in cmd2 ]
+
+        edgebodycmd = []
+        edgeheadcmd = []
+        for edge in graph.edges:
+            self.draw_edge(edge, edgebodycmd, edgeheadcmd, view)
+            
+        return edgebodycmd + nodebkgndcmd + edgeheadcmd + nodecmd
+
+class Command:
+    def __init__ (self, fun, *args):
+        self.fun = fun
+        self.args = args
+    def __call__ (self):
+        self.fun (*self.args)
+        
+from drawgraph import TextSnippet
+   
+class MyTextSnippet (TextSnippet):
+    
+    def __init__(self, driver, renderer, font,  text, fgcolor, bgcolor=None):
+        self.renderer = renderer
+        self.driver = driver
+        self.imgs = []
+        self.parts = []
+        import re
+        re_nonword=re.compile(r'([^0-9a-zA-Z_.]+)')
+        
+        parts = self.parts
+        for word in re_nonword.split(text):
+            if not word:
+                continue
+            if word in renderer.highlightwords:
+                fg, bg = renderer.wordcolor(word)
+                bg = bg or bgcolor
+            else:
+                fg, bg = fgcolor, bgcolor
+            parts.append((word, fg, bg))
+        # consolidate sequences of words with the same color
+        for i in range(len(parts)-2, -1, -1):
+            if parts[i][1:] == parts[i+1][1:]:
+                word, fg, bg = parts[i]
+                parts[i] = word + parts[i+1][0], fg, bg
+                del parts[i+1]
+        # delete None backgrounds
+        for i in range(len(parts)):
+            if parts[i][2] is None:
+                parts[i] = parts[i][:2]
+                
+                
+        # render parts
+        i = 0
+        while i < len(parts):
+            part = parts[i]
+            word = part[0]
+            img = self.driver.render_font (font, word, False, *part[1:])
+            if img:
+                self.imgs.append(img)
+                i += 1
+            
+            try:
+                
+                try:
+                    img = font.render(word, False, *part[1:])
+                except pygame.error, e:
+                    # Try *with* anti-aliasing to work around a bug in SDL
+                    img = font.render(word, True, *part[1:])
+                    
+            except pygame.error:
+                del parts[i]   # Text has zero width
+            else:
+                self.imgs.append(img)
+                i += 1
+                
+                   
+    def draw (self, x, y):
+        for part, img in zip(self.parts, self.imgs):
+            word = part[0]
+            self.renderer.driver.blit_image (img, (x, y))
+            
+            w, h = img.get_size()
+            self.renderer.textzones.append((x, y, w, h, word))
+            x += w
\ No newline at end of file

Added: pypy/branch/newdotviewer/test/test_graphclient.py
==============================================================================
--- (empty file)
+++ pypy/branch/newdotviewer/test/test_graphclient.py	Wed Dec 12 23:07:04 2007
@@ -0,0 +1,267 @@
+from dotviewer import graphclient 
+from dotviewer import protocol
+from dotviewer import graphpage
+
+import Queue   
+
+class MyPage (graphpage.GraphPage):
+    
+   def __init__(self, source):
+        self.source = source
+        
+   def content(self):
+       return self.source
+
+import threading
+import Queue
+
+from dotviewer.graphdisplay import GraphDisplay
+        
+class FakeDisplay:
+    "I am responsable to handle video state"
+    def __init__ (self, size= (100, 100)):
+        self.size = 100, 100
+        
+from dotviewer.display import MyDisplay
+
+class FakeImg:
+    fakeimg = True
+    def get_size(self):
+        return (0 , 0)
+    
+class FakeFont:
+    fakefont = True
+    def render (self, *args):
+        return FakeImg()
+    
+class FakeDriver:
+    
+    def highlight_color (self,color):
+        return None
+    
+    def getcolor (self, name, default):
+        return None
+    
+    def __init__(self):
+        self.events = []
+        self.actions = []
+        self.size = 100,100 
+        
+    def process_event (self, event, display):
+        self.events.append( event)
+        if event.get('layout', None):
+            self.set_layout (event.get('layout'))
+            
+    def get_canvas_size (self):
+        return 1000,1000 # WARNING this could affect a lot
+    
+        
+    def getfont (self, size, fixedfont=False):
+        return FakeFont()
+    
+    def render_font (self, font, *args):
+        return font.render (*args)
+    
+    def fill (self, *args):
+        self.actions.append (( "Filled ", args ))
+    
+    def draw_lines (self, *args):
+        self.actions.append (("drawing lines",args))
+        
+    def draw_rect (self, *args):
+        self.actions.append (( "drawing rect",args))
+        
+    def draw_polygon (self, *args):
+        self.actions.append (( "drawing rect",args))
+        
+    def draw_ellipsis(self, *args):
+        self.actions.append ((  "drawing rect",args))
+        
+    def blit_image (self, *args):
+        self.actions.append ((  "blitting image",args))
+    
+class DisplayService (threading.Thread):
+   def __init__ (self, display, **kwds):
+       threading.Thread.__init__(self,**kwds)
+       self.cmdqueue = Queue.Queue()
+       self.display = display
+       self.alive = False
+       
+   def post_event (self, **event):
+       self.cmdqueue.put (dict(**event))
+       
+   def stop (self):
+       self.alive = False
+          
+   def run (self):
+        self.dragging = self.click_origin = self.click_time = None
+        self.alive = True
+        try:
+
+            while self.alive:
+#                if self.must_redraw and not EventQueue:
+#                    self.redraw_now()
+
+#                if self.cmdqueue:
+#                    wait_for_events()
+                try:
+                    event = self.cmdqueue.get(block=True,timeout=0.5)
+                    self.display.process_event (event)
+                    
+                except Queue.Empty:
+                    pass
+        except StopIteration:
+            pass
+        
+    
+
+from dotviewer import drawgraph
+        
+from test_graphparse import graph_from_dot_buffer
+  
+
+
+           
+#class Frame:
+#    def __init__(self):
+#        self.x = 0
+#        self.y = 0
+#        self.width  = 100
+#        self.height = 100
+#        
+#    def at (self, x, y):
+#        self.x = x
+#        self.y = y
+#        
+#    def point (self, x,y):
+#        scale = 1.5
+#        return scale + (x * scale), (self.height - (y * scale)) 
+    
+    
+def test_view_width_must_be_scaled ():
+    display = FakeDisplay()
+    display.size = 1, 1
+    v = View (display)
+    v.margin = 0
+    v.setscale (100)
+    assert v.width == 100 
+    assert v.height == 100 
+    
+def test_scale_commands_doesnot_stacks ():
+    display = FakeDisplay((1,1))
+    v = View (display)
+    v.margin = 0
+    v.setscale (1)
+    
+    assert v.getboundingbox() == (0,0,100,100)
+    
+    v.setscale (10)
+    v.setscale (10)
+    assert v.getboundingbox() == (0,0,1000,1000) 
+
+def test_map ():
+    display = FakeDisplay()
+    display.size = 100, 100
+    v = View (display)
+    v.margin = 0
+    v.setscale (1.0)
+    assert v.map (0,0) == (0, 100)
+    
+def test_boundingbox():
+    display = FakeDisplay()
+    display.size = 100, 100
+    v = View (display)
+    v.margin = 0
+    v.setscale (1.0)
+    assert v.getboundingbox() == (0,0, 100, 100)
+
+def test_shift_offset ():
+    display = FakeDisplay()
+    display.size = 100, 100
+    v = View (display)
+    v.MARGIN = 0
+
+    v.setoffset(0, 0)
+    x,y,w,h = v.getboundingbox()
+    assert x == 0
+    assert y == 0
+
+    v.shiftoffset (10, 10)
+    x,y,w,h = v.getboundingbox()
+    assert x == -10
+    assert y == -10
+
+    assert h,w == display.size
+
+def test_simple_graph_must_be_visible ():
+    graph = graph_from_dot_buffer (test.ONE_NODE_GRAPH)
+    assert len(graph.nodes) == 2
+    display = FakeDisplay ()
+    view = View (display)
+    driver = FakeDriver()
+    
+    view.owidth, view.oheight = graph.boundingbox
+    view.calculate_scale_to_fit (graph.boundingbox)
+    
+    x,y,w,h = view.getboundingbox()
+    rw , rh = graph.boundingbox  
+    assert w,h == int (rw, rh)
+    
+    g = MyGraphRenderer (driver)
+    
+    for name,node in graph.nodes.iteritems():
+        assert view.visible_node (node.x, node.y, node.w, node.h)
+        
+    visible = g.computevisible (graph, view)
+    assert visible.nodes
+    assert visible.edges
+    
+    assert len(visible.nodes) == len(graph.nodes.values())
+    
+    assert len(visible.edges) == len(graph.edges)
+
+from dotviewer.render import MyGraphRenderer
+from dotviewer.display import View
+  
+def test_graph_renderer ():
+    graph = graph_from_dot_buffer (test.BIG_GRAPH)
+    driver = FakeDriver()
+    display = FakeDisplay ()
+    g = MyGraphRenderer (driver)
+    view = View (display)
+    g.render (graph, view)
+
+
+def test_view_fit_to_bbox ():
+    display = FakeDisplay((100,100))
+    view = View (display)
+
+    scale = view.calculate_scale_to_fit ((10,10))
+    
+    assert view.scale == 10
+
+def test_graph_renderer_with_pygame ():
+    #PYGAME
+    size = 800,600
+    display = MyDisplay (size)
+    
+    graph = graph_from_dot_buffer (test.ONE_NODE_GRAPH)
+    display.display_graph (graph)
+    display.driver.update()
+    
+    graph = graph_from_dot_buffer (test.BIG_GRAPH)
+    display.display_graph (graph)
+    display.loop()
+
+        
+from dotviewer import graphclient
+from dotviewer import graphpage
+from dotviewer import test
+
+import sys
+import os
+this_dir = os.path.dirname(os.path.abspath(__file__))
+GRAPHSERVER = os.path.join(this_dir, 'graphserver.py')
+
+
+    
\ No newline at end of file

Added: pypy/branch/newdotviewer/test/test_graphparse.py
==============================================================================
--- (empty file)
+++ pypy/branch/newdotviewer/test/test_graphparse.py	Wed Dec 12 23:07:04 2007
@@ -0,0 +1,350 @@
+from dotviewer import graphparse
+import os, sys, re
+
+from dotviewer import msgstruct as msgx
+
+from dotviewer.drawgraph import GraphLayout
+
+class GraphGenerator:
+    
+    def __init__ (self):
+        self._current = None 
+
+    def new_graph (self,graph_id, header):
+        name, scale, w, h = header
+        self._current = GraphLayout (float(scale), float(w),float(h))
+        
+    def new_node (self, node):
+        self._current.add_node (*node[1:])
+        
+    def new_edge (self, line):
+        self._current.add_edge (*line[1:])
+
+    def end (self):
+        pass
+    
+    def get_graph (self):
+        return self._current
+    
+class GraphParseEventGenerator:
+    def __init__ (self):
+        self._current = None
+         
+    def new_graph (self,graph_id, header):
+        self._current = []
+        item = (msgx.CMSG_START_GRAPH, graph_id) + tuple(header[1:])
+        self._current.append (item)
+        return item
+    
+    def new_node (self, line):
+        item = (msgx.CMSG_ADD_NODE,) + tuple(line[1:])
+        self._current.append (item)
+        return item
+    
+    def new_edge (self, line):
+        item = (msgx.CMSG_ADD_EDGE,) + tuple(line[1:])
+        self._current.append (item)
+        return item
+    
+    def new_link (self, word, statusbartext, color=(255,255,255)):
+        item = (msgx.CMSG_ADD_LINK, word,
+                               statusbartext, color[0], color[1], color[2])
+        self._current.append (item)
+        return item
+        
+    def fixed_font (self):
+        item = (msgx.CMSG_FIXED_FONT,)
+        self._current.append (item)
+        return item
+    
+    def end(self):
+        item = (msgx.CMSG_STOP_GRAPH,)
+        self._current.append(item)
+        return item
+    
+    def get_graph (self):
+        return self._current
+     
+re_nonword = re.compile(r'([^0-9a-zA-Z_.]+)')
+re_plain   = re.compile(r'graph [-0-9.]+ [-0-9.]+ [-0-9.]+$', re.MULTILINE)
+re_digraph = re.compile(r'\b(graph|digraph)\b', re.IGNORECASE)
+
+def guess_type(content):
+    # try to see whether it is a directed graph or not,
+    # or already a .plain file
+    # XXX not a perfect heursitic
+    if re_plain.match(content):
+        return 'plain'     # already a .plain file
+    # look for the word 'graph' or 'digraph' followed by a '{'.
+    bracepos = None
+    lastfound = ''
+    for match in re_digraph.finditer(content):
+        position = match.start()
+        if bracepos is None:
+            bracepos = content.find('{', position)
+            if bracepos < 0:
+                break
+        elif position > bracepos:
+            break
+        lastfound = match.group()
+    if lastfound.lower() == 'digraph':
+        return 'dot'
+    if lastfound.lower() == 'graph':
+        return 'neato'
+    print >> sys.stderr, "Warning: could not guess file type, using 'dot'"
+    return 'unknown'
+
+class DotTool:
+    cmdline = 'dot -Tplain'
+    
+    def convert (self,content):
+        child_in, child_out = os.popen2 (self.cmdline)
+        def bkgndwrite(f, data):
+            f.write(data)
+            f.close()
+        try:
+            import thread
+        except ImportError:
+            bkgndwrite(child_in, content)
+        else:
+            thread.start_new_thread(bkgndwrite, (child_in, content))
+        plaincontent = child_out.read()
+        child_out.close()
+        
+        return plaincontent
+    
+def convert_using_tool (content, contenttype):
+        if contenttype == 'plain':
+            return content
+
+        if contenttype != 'neato':
+            cmdline = 'dot -Tplain'
+        else:
+            cmdline = 'neato -Tplain'
+        return DotTool().convert (content)
+ 
+def convert_using_codespeak (content, contenttype):
+        import urllib
+        request = urllib.urlencode({'dot': content})
+        url = 'http://codespeak.net/pypy/convertdot.cgi'
+        print >> sys.stderr, '* posting:', url
+        g = urllib.urlopen(url, data=request)
+        result = []
+        while True:
+            data = g.read(16384)
+            if not data:
+                break
+            result.append(data)
+        g.close()
+        plaincontent = ''.join(result)
+        # very simple-minded way to give a somewhat better error message
+        if plaincontent.startswith('<body'):
+            raise Exception("the dot on codespeak has very likely crashed")
+        return contenttype
+    
+def dot2plain(content, contenttype, use_codespeak=False):
+    if contenttype == 'plain':
+        # already a .plain file
+        return content
+
+    if not use_codespeak:
+        return convert_using_tool (content, contenttype)
+    else:
+        return convert_using_codespeak(content, contenttype)
+ 
+
+def splitline(line, re_word = re.compile(r'[^\s"]\S*|["]["]|["].*?[^\\]["]')):
+    result = []
+    for word in re_word.findall(line):
+        if word.startswith('"'):
+            word = eval(word)
+        result.append(word)
+    return result
+
+def parse_body (lines, generator, texts):
+    for line in lines:
+        line = splitline(line)
+        if line[0] == 'node':
+            if len(line) != 11:
+                raise PlainParseError("bad 'node'")
+            generator.new_node (line) 
+            texts.append(line[6])
+        if line[0] == 'edge':
+            generator.new_edge (line)
+            i = 4 + 2 * int(line[3])
+            if len(line) > i + 2:
+                texts.append(line[i])
+        if line[0] == 'stop':
+            break
+
+def extract_header (lines):
+    header = splitline(lines.pop(0))
+    if header[0] != 'graph':
+        raise PlainParseError("should start with 'graph'")
+    return header
+
+def normalize_lines (plaincontent):
+    lines = plaincontent.splitlines(True)
+    
+    for i in xrange(len(lines)-2, -1, -1):
+        if lines[i].endswith('\\\n'):   # line ending in '\'
+            lines[i] = lines[i][:-2] + lines[i+1]
+            del lines[i+1]
+    return lines
+
+def generate_links (text):
+        # only include the links that really appear in the graph
+        seen = {}
+        for text in texts:
+            for word in re_nonword.split(text):
+                if word and word in links and word not in seen:
+                    t = links[word]
+                    if isinstance(t, tuple):
+                        statusbartext, color = t
+                    else:
+                        statusbartext = t
+                        color = None
+                    generator.new_link (word, statusbartext, color)
+                    seen[word] = True
+                    
+def parse_plain(graph_id, plaincontent, links={}, fixedfont=False, generator=GraphParseEventGenerator()):
+    lines = normalize_lines (plaincontent)
+    generator.new_graph (graph_id, extract_header(lines))
+
+    texts = []
+    parse_body(lines, generator,texts)
+    
+    if links:
+        generate_links(text)
+
+
+class PlainParseError(Exception):
+    pass
+
+def my_parse_dot ( graph_id, content, links={}, fixedfont = False):
+    contenttype = guess_type(content)
+    message_list = None
+    
+    try:
+        plaincontent = dot2plain(content, contenttype, use_codespeak=False)
+    except PlainParseError:
+        # failed, retry via codespeak
+        plaincontent = dot2plain(content, contenttype, use_codespeak=True)
+        
+    generator = GraphParseEventGenerator()       
+    parse_plain(graph_id, plaincontent, links, fixedfont, generator)
+    if fixedfont:
+        generator.fixed_font ()
+    generator.end()
+    return generator.get_graph ()
+
+class DotParseError(Exception):
+    pass
+class DotReader:
+    def __init__ (self):
+        self.tool = DotTool()
+        self.format = re.compile(r'[^\s"]\S*|["]["]|["].*?[^\\]["]')
+        
+    def select_tool (self, buffer):
+        return self.tool
+    
+    def read_words (self, line):
+        result = []
+        for word in self.format.findall (line):
+            if word.startswith('"'):
+                word = eval(word)
+            result.append(word)
+        return result
+    
+    def traverse (self, buffer, visitor):
+        tool = self.select_tool (buffer)
+        converted = tool.convert (buffer)
+
+        lines = self.collapse_lines (converted)
+        header = self.read_words(lines.pop(0))
+
+        if header[0] != 'graph':
+            raise DotParseError("should start with 'graph'")
+        
+        visitor.new_graph (0, header)
+        text = self.parse_body (lines, visitor)
+        self.generate_links (text)
+        res = visitor.end()
+        
+    def generate_links (self,texts, links={}):
+        # only include the links that really appear in the graph
+        seen = {}
+        for text in texts:
+            for word in re_nonword.split(text):
+                if word and word in links and word not in seen:
+                    t = links[word]
+                    if isinstance(t, tuple):
+                        statusbartext, color = t
+                    else:
+                        statusbartext = t
+                        color = None
+                    generator.new_link (word, statusbartext, color)
+                    seen[word] = True
+                    
+    def parse_node (self, words):
+        pass
+            
+    def parse_body (self, lines, generator):
+        texts = []
+        for line in lines:
+            line = self.read_words (line)
+            if line[0] == 'node':
+                if len(line) != 11:
+                    raise PlainParseError("bad 'node'")
+                generator.new_node (line) 
+                texts.append(line[6])
+            if line[0] == 'edge':
+                generator.new_edge (line)
+                i = 4 + 2 * int(line[3])
+                if len(line) > i + 2:
+                    texts.append(line[i])
+            if line[0] == 'stop':
+                break
+        return texts
+            
+    def collapse_lines (self, buffer):
+        lines = buffer.splitlines(True)
+        for i in xrange(len(lines)-2, -1, -1):
+            if lines[i].endswith('\\\n'):   # line ending in '\'
+                lines[i] = lines[i][:-2] + lines[i+1]
+                del lines[i+1]
+        return lines
+
+def graph_from_dot_buffer (dotbuffer):
+    reader = DotReader ()
+    visitor = GraphGenerator()
+    reader.traverse (dotbuffer, visitor)
+    return visitor.get_graph()
+
+from dotviewer import test
+def test_graph_dot_buffer ():
+    graph = graph_from_dot_buffer (test.ONE_NODE_GRAPH)
+    assert len(graph.nodes) == 2
+    
+def test_graph_dot_buffer ():
+    graph = graph_from_dot_buffer ('digraph {')
+    assert len(graph.nodes) == 0
+
+def xxtest_graphparse ():
+    g = graphparse.parse_plain ("id", graphparse.dot2plain(test.BIG_GRAPH, ''))
+    result = [ m for m in g]
+    assert result == my_parse_dot ("id", test.BIG_GRAPH)
+    
+    g = graphparse.parse_plain ("id", graphparse.dot2plain(test.BIG_GRAPH, ''), fixedfont=False)
+    result = [ m for m in g]
+    my_result = my_parse_dot("id", test.BIG_GRAPH,fixedfont=False)
+    assert result == my_result
+    
+def xtest_remote_codespeak ():    
+    g1 = graphparse.parse_dot("id", graphparse.dot2plain(SOURCE1, '', use_codespeak=True))
+    g2 = graphparse.parse_dot("id", dot2plain(SOURCE1, '', use_codespeak=True))
+
+    for a,b in zip(g1,g2):
+        assert a == b
+        
+    
\ No newline at end of file

Added: pypy/branch/newdotviewer/test/test_service.py
==============================================================================
--- (empty file)
+++ pypy/branch/newdotviewer/test/test_service.py	Wed Dec 12 23:07:04 2007
@@ -0,0 +1,13 @@
+from test_graphclient import  DisplayService, graph_from_dot_buffer
+
+from dotviewer import protocol, test
+from dotviewer.display import MyDisplay 
+from drivers import PyGameDriver 
+
+def test_display_service ():
+    page = graph_from_dot_buffer(test.BIG_GRAPH)
+    width, height = 800, 600
+    display = MyDisplay ((width, height))
+    service = DisplayService (display)
+    service.start()
+    service.post_event (layout= page)

Modified: pypy/branch/newdotviewer/test/test_translator.py
==============================================================================
--- pypy/branch/newdotviewer/test/test_translator.py	(original)
+++ pypy/branch/newdotviewer/test/test_translator.py	Wed Dec 12 23:07:04 2007
@@ -47,7 +47,7 @@
     graph = graph_from_dot_buffer (page.source) 
     dis = display.MyDisplay ((800,600))
     dis.display_graph (graph)
-    dis.driver.loop ()
+    dis.loop ()
 
 def show_callgraph (test):
     """Shows the whole call graph and the class hierarchy, based on



More information about the Pypy-commit mailing list