... | ... |
@@ -56,10 +56,6 @@ class Shape: |
56 | 56 |
"""Draw this shape with the given cairo context""" |
57 | 57 |
raise NotImplementedError |
58 | 58 |
|
59 |
- def boundingbox(self): |
|
60 |
- """Get the bounding box of this shape.""" |
|
61 |
- raise NotImplementedError |
|
62 |
- |
|
63 | 59 |
|
64 | 60 |
class TextShape(Shape): |
65 | 61 |
|
... | ... |
@@ -222,31 +218,63 @@ class Element(CompoundShape): |
222 | 218 |
def get_url(self, x, y): |
223 | 219 |
return None |
224 | 220 |
|
221 |
+ def get_jump(self, x, y): |
|
222 |
+ return None |
|
223 |
+ |
|
225 | 224 |
|
226 | 225 |
class Node(Element): |
227 | 226 |
|
228 | 227 |
def __init__(self, x, y, w, h, shapes, url): |
229 | 228 |
Element.__init__(self, shapes) |
230 | 229 |
|
231 |
- self.x1 = x - w/2 |
|
232 |
- self.y1 = y - h/2 |
|
233 |
- self.x2 = x + w/2 |
|
234 |
- self.y2 = y + h/2 |
|
230 |
+ self.x = x |
|
231 |
+ self.y = y |
|
232 |
+ |
|
233 |
+ self.x1 = x - 0.5*w |
|
234 |
+ self.y1 = y - 0.5*h |
|
235 |
+ self.x2 = x + 0.5*w |
|
236 |
+ self.y2 = y + 0.5*h |
|
235 | 237 |
|
236 | 238 |
self.url = url |
237 | 239 |
|
240 |
+ def is_inside(self, x, y): |
|
241 |
+ return self.x1 <= x and x <= self.x2 and self.y1 <= y and y <= self.y2 |
|
242 |
+ |
|
238 | 243 |
def get_url(self, x, y): |
239 | 244 |
if self.url is None: |
240 | 245 |
return None |
241 | 246 |
#print (x, y), (self.x1, self.y1), "-", (self.x2, self.y2) |
242 |
- if self.x1 <= x and x <= self.x2 and self.y1 <= y and y <= self.y2: |
|
247 |
+ if self.is_inside(x, y): |
|
243 | 248 |
return self.url |
244 | 249 |
return None |
245 | 250 |
|
251 |
+ def get_jump(self, x, y): |
|
252 |
+ if self.is_inside(x, y): |
|
253 |
+ return self.x, self.y |
|
254 |
+ return None |
|
255 |
+ |
|
256 |
+ |
|
257 |
+def square_distance(x1, y1, x2, y2): |
|
258 |
+ deltax = x2 - x1 |
|
259 |
+ deltay = y2 - y1 |
|
260 |
+ return deltax*deltax + deltay*deltay |
|
261 |
+ |
|
246 | 262 |
|
247 | 263 |
class Edge(Element): |
248 | 264 |
|
249 |
- pass |
|
265 |
+ def __init__(self, points, shapes): |
|
266 |
+ Element.__init__(self, shapes) |
|
267 |
+ |
|
268 |
+ self.points = points |
|
269 |
+ |
|
270 |
+ RADIUS = 10 |
|
271 |
+ |
|
272 |
+ def get_jump(self, x, y): |
|
273 |
+ if square_distance(x, y, *self.points[0]) <= self.RADIUS*self.RADIUS: |
|
274 |
+ return self.points[-1] |
|
275 |
+ if square_distance(x, y, *self.points[-1]) <= self.RADIUS*self.RADIUS: |
|
276 |
+ return self.points[0] |
|
277 |
+ return None |
|
250 | 278 |
|
251 | 279 |
|
252 | 280 |
class Graph(Shape): |
... | ... |
@@ -280,6 +308,17 @@ class Graph(Shape): |
280 | 308 |
return url |
281 | 309 |
return None |
282 | 310 |
|
311 |
+ def get_jump(self, x, y): |
|
312 |
+ for edge in self.edges: |
|
313 |
+ jump = edge.get_jump(x, y) |
|
314 |
+ if jump is not None: |
|
315 |
+ return jump |
|
316 |
+ for node in self.nodes: |
|
317 |
+ jump = node.get_jump(x, y) |
|
318 |
+ if jump is not None: |
|
319 |
+ return jump |
|
320 |
+ return None |
|
321 |
+ |
|
283 | 322 |
|
284 | 323 |
class XDotAttrParser: |
285 | 324 |
"""Parser for xdot drawing attributes. |
... | ... |
@@ -441,7 +480,7 @@ class XDotParser: |
441 | 480 |
for node in graph.get_node_list(): |
442 | 481 |
if node.pos is None: |
443 | 482 |
continue |
444 |
- x, y = map(float, node.pos.split(",")) |
|
483 |
+ x, y = self.parse_node_pos(node.pos) |
|
445 | 484 |
w = float(node.width)*72 |
446 | 485 |
h = float(node.height)*72 |
447 | 486 |
shapes = [] |
... | ... |
@@ -454,16 +493,36 @@ class XDotParser: |
454 | 493 |
nodes.append(Node(x, y, w, h, shapes, url)) |
455 | 494 |
|
456 | 495 |
for edge in graph.get_edge_list(): |
496 |
+ if edge.pos is None: |
|
497 |
+ continue |
|
498 |
+ points = self.parse_edge_pos(edge.pos) |
|
457 | 499 |
shapes = [] |
458 | 500 |
for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"): |
459 | 501 |
if hasattr(edge, attr): |
460 | 502 |
parser = XDotAttrParser(self, getattr(edge, attr)) |
461 | 503 |
shapes.extend(parser.parse()) |
462 | 504 |
if shapes: |
463 |
- edges.append(Edge(shapes)) |
|
505 |
+ edges.append(Edge(points, shapes)) |
|
464 | 506 |
|
465 | 507 |
return Graph(width, height, nodes, edges) |
466 | 508 |
|
509 |
+ def parse_node_pos(self, pos): |
|
510 |
+ x, y = pos.split(",") |
|
511 |
+ return self.transform(float(x), float(y)) |
|
512 |
+ |
|
513 |
+ def parse_edge_pos(self, pos): |
|
514 |
+ points = [] |
|
515 |
+ for entry in pos.split(' '): |
|
516 |
+ fields = entry.split(',') |
|
517 |
+ try: |
|
518 |
+ x, y = fields |
|
519 |
+ except ValueError: |
|
520 |
+ # TODO: handle start/end points |
|
521 |
+ continue |
|
522 |
+ else: |
|
523 |
+ points.append(self.transform(float(x), float(y))) |
|
524 |
+ return points |
|
525 |
+ |
|
467 | 526 |
def transform(self, x, y): |
468 | 527 |
# XXX: this is not the right place for this code |
469 | 528 |
x = (x + self.xoffset)*self.xscale |
... | ... |
@@ -472,6 +531,7 @@ class XDotParser: |
472 | 531 |
|
473 | 532 |
|
474 | 533 |
class DotWidget(gtk.DrawingArea): |
534 |
+ """PyGTK widget that draws dot graphs.""" |
|
475 | 535 |
|
476 | 536 |
__gsignals__ = { |
477 | 537 |
'expose-event': 'override', |
... | ... |
@@ -613,6 +673,13 @@ class DotWidget(gtk.DrawingArea): |
613 | 673 |
self.emit('clicked', unicode(url), event) |
614 | 674 |
return True |
615 | 675 |
|
676 |
+ jump = self.get_jump(x, y) |
|
677 |
+ x, y = self.window2graph(x, y) |
|
678 |
+ if jump is not None: |
|
679 |
+ jumpx, jumpy = jump |
|
680 |
+ self.x += jumpx - x |
|
681 |
+ self.y += jumpy - y |
|
682 |
+ self.queue_draw() |
|
616 | 683 |
return False |
617 | 684 |
|
618 | 685 |
def on_area_button_release(self, area, event): |
... | ... |
@@ -652,7 +719,7 @@ class DotWidget(gtk.DrawingArea): |
652 | 719 |
|
653 | 720 |
return True |
654 | 721 |
|
655 |
- def get_url(self, x, y): |
|
722 |
+ def window2graph(self, x, y): |
|
656 | 723 |
rect = self.get_allocation() |
657 | 724 |
x -= 0.5*rect.width |
658 | 725 |
y -= 0.5*rect.height |
... | ... |
@@ -660,9 +727,16 @@ class DotWidget(gtk.DrawingArea): |
660 | 727 |
y /= self.zoom_ratio |
661 | 728 |
x += self.x |
662 | 729 |
y += self.y |
663 |
- y = self.graph.height - y |
|
730 |
+ return x, y |
|
731 |
+ |
|
732 |
+ def get_url(self, x, y): |
|
733 |
+ x, y = self.window2graph(x, y) |
|
664 | 734 |
return self.graph.get_url(x, y) |
665 | 735 |
|
736 |
+ def get_jump(self, x, y): |
|
737 |
+ x, y = self.window2graph(x, y) |
|
738 |
+ return self.graph.get_jump(x, y) |
|
739 |
+ |
|
666 | 740 |
|
667 | 741 |
class DotWindow(gtk.Window): |
668 | 742 |
|