[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