| ... | ... |
@@ -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 |
|