Browse code

Cleanup xdot parser.

Jose.R.Fonseca authored on 03/01/2008 12:36:29
Showing 1 changed files

  • xdot.py index 74cb6fe..f67344b 100644
... ...
@@ -31,6 +31,7 @@ class Pen:
31 31
 	"""Store pen attributes."""
32 32
 
33 33
 	def __init__(self):
34
+		# set default attributes
34 35
 		self.color = (0.0, 0.0, 0.0, 1.0)
35 36
 		self.fillcolor = (0.0, 0.0, 0.0, 1.0)
36 37
 		self.linewidth = 1.0
... ...
@@ -211,6 +212,74 @@ class CompoundShape(Shape):
211 212
 			shape.draw(cr)
212 213
 
213 214
 
215
+class Element(CompoundShape):
216
+	"""Base class for graph nodes and edges."""
217
+
218
+	def __init__(self, shapes):
219
+		CompoundShape.__init__(self, shapes)
220
+	
221
+	def get_url(self, x, y):
222
+		return None
223
+
224
+
225
+class Node(Element):
226
+
227
+	def __init__(self, x, y, w, h, shapes, url):
228
+		Element.__init__(self, shapes)
229
+		
230
+		self.x1 = x - w/2
231
+		self.y1 = y - h/2
232
+		self.x2 = x + w/2
233
+		self.y2 = y + h/2
234
+		
235
+		self.url = url
236
+
237
+	def get_url(self, x, y):
238
+		if self.url is None:
239
+			return None
240
+		#print (x, y), (self.x1, self.y1), "-", (self.x2, self.y2)
241
+		if self.x1 <= x and x <= self.x2 and self.y1 <= y and y <= self.y2:
242
+			return self.url
243
+		return None
244
+
245
+
246
+class Edge(Element):
247
+
248
+	pass
249
+
250
+
251
+class Graph(Shape):
252
+
253
+	def __init__(self, width=1, height=1, nodes=(), edges=()):
254
+		Shape.__init__(self)
255
+		
256
+		self.width = width
257
+		self.height = height
258
+		self.nodes = nodes
259
+		self.edges = edges
260
+
261
+	def get_size(self):
262
+		return self.width, self.height
263
+
264
+	def draw(self, cr):
265
+		cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
266
+
267
+		cr.set_line_cap(cairo.LINE_CAP_BUTT)
268
+		cr.set_line_join(cairo.LINE_JOIN_MITER)
269
+		
270
+		for edge in self.edges:
271
+			edge.draw(cr)
272
+		for node in self.nodes:
273
+			node.draw(cr)
274
+	
275
+	def get_url(self, x, y):
276
+		for node in self.nodes:
277
+			url = node.get_url(x, y)
278
+			if url is not None:
279
+				return url
280
+		return None
281
+
282
+
214 283
 class XDotAttrParser:
215 284
 	"""Parser for xdot drawing attributes.
216 285
 	See also:
... ...
@@ -336,24 +405,69 @@ class XDotAttrParser:
336 405
 			else:
337 406
 				sys.stderr.write("unknown xdot opcode '%s'\n" % op)
338 407
 				break
339
-		return CompoundShape(shapes)
408
+		return shapes
340 409
 
341 410
 	def transform(self, x, y):
342 411
 		return self.parser.transform(x, y)
343 412
 
344 413
 
345
-class Hyperlink:
414
+class XDotParser:
415
+	
416
+	def __init__(self, xdotcode):
417
+		self.xdotcode = xdotcode
346 418
 
347
-	def __init__(self, url, x, y, w, h):
348
-		self.url = url
349
-		self.x1 = x - w/2
350
-		self.y1 = y - h/2
351
-		self.x2 = x + w/2
352
-		self.y2 = y + h/2
419
+	def parse(self):
420
+		graph = pydot.graph_from_dot_data(self.xdotcode)
353 421
 
354
-	def hit(self, x, y):
355
-		#print (x, y), (self.x1, self.y1), "-", (self.x2, self.y2)
356
-		return self.x1 <= x and x <= self.x2 and self.y1 <= y and y <= self.y2
422
+		bb = graph.get_bb()
423
+		if bb is None:
424
+			return []
425
+
426
+		xmin, ymin, xmax, ymax = map(int, bb.split(","))
427
+
428
+		self.xoffset = -xmin
429
+		self.yoffset = -ymax
430
+		self.xscale = 1.0
431
+		self.yscale = -1.0
432
+		# FIXME: scale from points to pixels
433
+
434
+		width = xmax - xmin
435
+		height = ymax - ymin
436
+
437
+		nodes = []
438
+		edges = []
439
+		
440
+		for node in graph.get_node_list():
441
+			if node.pos is None:
442
+				continue
443
+			x, y = map(float, node.pos.split(","))
444
+			w = float(node.width)*72
445
+			h = float(node.height)*72
446
+			shapes = []
447
+			for attr in ("_draw_", "_ldraw_"):
448
+				if hasattr(node, attr):
449
+					parser = XDotAttrParser(self, getattr(node, attr))
450
+					shapes.extend(parser.parse())
451
+			url = node.URL
452
+			if shapes:
453
+				nodes.append(Node(x, y, w, h, shapes, url))
454
+
455
+		for edge in graph.get_edge_list():
456
+			shapes = []
457
+			for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"):
458
+				if hasattr(edge, attr):
459
+					parser = XDotAttrParser(self, getattr(edge, attr))
460
+					shapes.extend(parser.parse())
461
+			if shapes:
462
+				edges.append(Edge(shapes))
463
+
464
+		return Graph(width, height, nodes, edges)
465
+
466
+	def transform(self, x, y):
467
+		# XXX: this is not the right place for this code
468
+		x = (x + self.xoffset)*self.xscale
469
+		y = (y + self.yoffset)*self.yscale
470
+		return x, y
357 471
 
358 472
 
359 473
 class DotWindow(gtk.Window):
... ...
@@ -374,11 +488,7 @@ class DotWindow(gtk.Window):
374 488
 	def __init__(self):
375 489
 		gtk.Window.__init__(self)
376 490
 
377
-		self.graph = None
378
-		self.width = 1
379
-		self.height = 1
380
-		self.shapes = []
381
-		self.hyperlinks = []
491
+		self.graph = Graph()
382 492
 
383 493
 		window = self
384 494
 
... ...
@@ -416,14 +526,8 @@ class DotWindow(gtk.Window):
416 526
 		toolbar = uimanager.get_widget('/ToolBar')
417 527
 		vbox.pack_start(toolbar, False)
418 528
 
419
-		# TODO: Use a custom widget instead of Layout like in the scrollable.py example?
420
-		#scrolled_window = self.scrolled_window = gtk.ScrolledWindow()
421
-		#scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
422
-		#vbox.pack_start(scrolled_window)
423
-
424 529
 		self.area = gtk.DrawingArea()
425 530
 		self.area.connect("expose_event", self.on_expose)
426
-		#scrolled_window.add(self.area)
427 531
 		vbox.pack_start(self.area)
428 532
 
429 533
 		self.area.set_flags(gtk.CAN_FOCUS)
... ...
@@ -440,7 +544,6 @@ class DotWindow(gtk.Window):
440 544
 
441 545
 		self.x, self.y = 0.0, 0.0
442 546
 		self.zoom_ratio = 1.0
443
-		self.pixbuf = None
444 547
 
445 548
 		self.show_all()
446 549
 
... ...
@@ -453,50 +556,13 @@ class DotWindow(gtk.Window):
453 556
 			universal_newlines=True
454 557
 		)
455 558
 		xdotcode = p.communicate(dotcode)[0]
456
-		#sys.stdout.write(xdotcode)
457
-		self.parse(xdotcode)
458
-		self.zoom_image(self.zoom_ratio, center=True)
459
-
460
-	def parse(self, xdotcode):
461
-		self.graph = pydot.graph_from_dot_data(xdotcode)
559
+		self.set_xdotcode(xdotcode)
462 560
 
463
-		bb = self.graph.get_bb()
464
-		if bb is None:
465
-			return
466
-
467
-		xmin, ymin, xmax, ymax = map(int, bb.split(","))
468
-
469
-		self.xoffset = -xmin
470
-		self.yoffset = -ymax
471
-		self.xscale = 1.0
472
-		self.yscale = -1.0
473
-		self.width = xmax - xmin
474
-		self.height = ymax - ymin
475
-
476
-		self.shapes = []
477
-		self.hyperlinks = []
478
-
479
-		for node in self.graph.get_node_list():
480
-			for attr in ("_draw_", "_ldraw_"):
481
-				if hasattr(node, attr):
482
-					p = XDotAttrParser(self, getattr(node, attr))
483
-					self.shapes.append(p.parse())
484
-			if node.URL is not None:
485
-				x, y = map(float, node.pos.split(","))
486
-				w = float(node.width)*72
487
-				h = float(node.height)*72
488
-				self.hyperlinks.append(Hyperlink(node.URL, x, y, w, h))
489
-		for edge in self.graph.get_edge_list():
490
-			for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"):
491
-				if hasattr(edge, attr):
492
-					p = XDotAttrParser(self, getattr(edge, attr))
493
-					self.shapes.append(p.parse())
494
-
495
-	def transform(self, x, y):
496
-		# XXX: this is not the right place for this code
497
-		x = (x + self.xoffset)*self.xscale
498
-		y = (y + self.yoffset)*self.yscale
499
-		return x, y
561
+	def set_xdotcode(self, xdotcode):
562
+		#print xdotcode
563
+		parser = XDotParser(xdotcode)
564
+		self.graph = parser.parse()
565
+		self.zoom_image(self.zoom_ratio, center=True)
500 566
 
501 567
 	def on_expose(self, area, event):
502 568
 		cr = area.window.cairo_create()
... ...
@@ -516,15 +582,7 @@ class DotWindow(gtk.Window):
516 582
 		cr.scale(self.zoom_ratio, self.zoom_ratio)
517 583
 		cr.translate(-self.x, -self.y)
518 584
 
519
-		# FIXME: scale from points to pixels
520
-
521
-		cr.set_source_rgba(0.0, 0.0, 0.0, 1.0)
522
-
523
-		cr.set_line_cap(cairo.LINE_CAP_BUTT)
524
-		cr.set_line_join(cairo.LINE_JOIN_MITER)
525
-		
526
-		for shape in self.shapes:
527
-			shape.draw(cr)
585
+		self.graph.draw(cr)
528 586
 
529 587
 		return False
530 588
 
... ...
@@ -538,8 +596,8 @@ class DotWindow(gtk.Window):
538 596
 
539 597
 	def zoom_image(self, zoom_ratio, center=False):
540 598
 		if center:
541
-			self.x = self.width/2
542
-			self.y = self.height/2
599
+			self.x = self.graph.width/2
600
+			self.y = self.graph.height/2
543 601
 		self.zoom_ratio = zoom_ratio
544 602
 		self.area.queue_draw()
545 603
 
... ...
@@ -554,8 +612,8 @@ class DotWindow(gtk.Window):
554 612
 	def on_zoom_fit(self, action):
555 613
 		rect = self.area.get_allocation()
556 614
 		zoom_ratio = min(
557
-			float(rect.width)/float(self.width),
558
-			float(rect.height)/float(self.height)
615
+			float(rect.width)/float(self.graph.width),
616
+			float(rect.height)/float(self.graph.height)
559 617
 		)
560 618
 		self.zoom_image(zoom_ratio, center=True)
561 619
 
... ...
@@ -651,12 +709,8 @@ class DotWindow(gtk.Window):
651 709
 		y /= self.zoom_ratio
652 710
 		x += self.x
653 711
 		y += self.y
654
-		y = self.height - y
655
-
656
-		for hyperlink in self.hyperlinks:
657
-			if hyperlink.hit(x, y):
658
-				return hyperlink.url
659
-		return None
712
+		y = self.graph.height - y
713
+		return self.graph.get_url(x, y)
660 714
 
661 715
 	def on_url_clicked(self, url, event):
662 716
 		return False