New test. Viewer improvements.
authorJorge Gorbe <j.gorbe@stcsl.es>
Wed, 31 Oct 2012 11:44:27 +0000 (12:44 +0100)
committerJorge Gorbe <j.gorbe@stcsl.es>
Wed, 31 Oct 2012 11:44:27 +0000 (12:44 +0100)
samples/script.py [deleted file]
samples/test3.cc [new file with mode: 0644]
samples/view.py [new file with mode: 0644]

diff --git a/samples/script.py b/samples/script.py
deleted file mode 100644 (file)
index b93f299..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-import gdb
-import gtk
-import pangocairo
-
-def print_node(node, recursion_level):
-    if len(node.type.fields()) == 0:
-        print "node = %s", str(node)
-        result = str(node)
-    else:
-        result = {}
-        for f in node.type.fields():
-            child = node[f.name]
-            if child.type.code == gdb.TYPE_CODE_PTR and recursion_level > 0:
-                if (child == 0):
-                    result_child = "NULL"
-                else:
-                    result_child = print_node(child.dereference(), recursion_level - 1)
-            else:
-                result_child = str(child)
-            result[f.name] = result_child
-    return result
-
-
-
-class GraphViewer(gtk.DrawingArea):
-    def __init__(self):
-        super(gtk.DrawingArea, self).__init__()
-        self.connect("expose_event", self.expose)
-
-    def expose(self, widget, event):
-        context = widget.window.cairo_create()
-        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
-        context.clip()
-        self.draw(context)
-        return False
-
-    def draw(self, context):
-        rect = self.get_allocation() # widget dimensions?
-
-        pangocairo_context = pangocairo.CairoContext(context)
-        layout = pangocairo_context.create_layout()
-        layout.set_text("Trololol")
-        context.move_to(100,100)
-        pangocairo_context.show_layout(layout)
-
-
-        #context.set_source_rgb(1.0, 0.0, 0.0)
-        context.move_to(0,0)
-        context.line_to(200,100)
-        context.set_line_width(4)
-        context.stroke()
-
-        context.rectangle(200,200,50,50)
-        context.set_line_width(1)
-        context.stroke()
-        
-
-
-
-class ViewerCommand(gdb.Command):
-    """View data structures"""
-    def __init__(self):
-        super(ViewerCommand, self).__init__("view", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
-
-
-    def invoke(self, arg, from_tty):
-        args = gdb.string_to_argv(arg)
-        value = gdb.parse_and_eval(args[0])
-
-        window = gtk.Window()
-        viewer = GraphViewer()
-        window.add(viewer)
-        window.connect("destroy", gtk.main_quit)
-        window.set_default_size(600,600)
-        window.show_all()
-        gtk.main()
-
-        #if len(args) > 1:
-        #    recursion_level = int(args[1])
-        #else:
-        #    recursion_level = 3
-        #print print_node(value.dereference(), recursion_level)
-
-
-        
-
-
-
-ViewerCommand()
-
-
diff --git a/samples/test3.cc b/samples/test3.cc
new file mode 100644 (file)
index 0000000..17196d5
--- /dev/null
@@ -0,0 +1,19 @@
+struct treenode {
+    int value;
+    treenode *left;
+    treenode *right;
+    treenode(): value(-1), left(0), right(0) {}
+};
+
+
+int main() {
+    treenode *root = new treenode;
+    root->left = new treenode;
+    root->right = new treenode;
+    root->left->left = new treenode;
+    root->left->right = new treenode;
+    root->left->left->left = new treenode;
+    root->left->left->right = new treenode;
+
+    return 0;
+}
diff --git a/samples/view.py b/samples/view.py
new file mode 100644 (file)
index 0000000..f4ba35b
--- /dev/null
@@ -0,0 +1,274 @@
+import gdb
+import gtk
+import pango
+import pangocairo
+import math
+
+def segment_intersection(A, B, C, D):
+    """Computes the intersection between the rect segment AB and the rect segment CD.
+    All parameters are 2D points expressed as (x,y) tuples. Result is also a (x,y) tuple, or
+    None if the segments don't intersect."""
+    # see http://stackoverflow.com/a/565282 for the explanation
+    def cross2D(A, B):
+        return A[0]*B[1]-A[1]*B[0]
+
+    def diff2D(A, B):
+        return (A[0]-B[0], A[1]-B[1])
+
+    p = A
+    r = diff2D(B, A)
+    q = C
+    s = diff2D(D, C)
+
+    r_cross_s = cross2D(r, s)
+    if abs(r_cross_s) < 1e-5: return None
+
+    q_minus_p = diff2D(q, p)
+    t = cross2D(q_minus_p, s) / r_cross_s
+    u = cross2D(q_minus_p, r) / r_cross_s
+
+    if t < 0.0 or t > 1.0 or u < 0.0 or u > 1.0: return None
+    else:
+        x = p[0] + t*r[0]
+        y = p[1] + t*r[1]
+        return (x,y)
+
+def node_segment_intersection(node, A, B):
+    """Computes the intersection point between the rect AB and a node"""
+    P1 = (node.x, node.y)
+    P2 = (node.x + node.width, node.y)
+    P3 = (node.x + node.width, node.y + node.height)
+    P4 = (node.x, node.y + node.height)
+    return segment_intersection(A, B, P1, P2) or segment_intersection(A, B, P2, P3) or segment_intersection(A, B, P3, P4) or segment_intersection(A, B, P4, P1)
+
+
+def node_connection_points(node1, node2):
+    def node_center(n): return (n.x + 0.5*n.width, n.y + 0.5*n.height)
+    c1 = node_center(node1)
+    c2 = node_center(node2)
+    return (node_segment_intersection(node1, c1, c2), node_segment_intersection(node2, c1, c2))
+
+
+def orientation(vx, vy):
+    """returns the angle of the vector (vx, vy) respect to (1, 0)"""
+    r = math.hypot(vx, vy)
+    vx = vx/r
+    vy = vy/r
+    angle = math.acos(vx)
+    if vy > 0: angle = 2*math.pi-angle
+    return angle
+
+def arrow_points(A, B):
+    """coordinates of two points (R1, R2) that form an arrow head for the segment AB, such that
+       plotting the lines R1B and R2B result in an arrow head"""
+    ARROW_RADIUS = 10
+    ARROW_ANGLE = math.radians(15)
+    ax, ay = A
+    bx, by = B
+    # compute the angle of AB wrt the origin
+    vx = bx-ax
+    vy = by-ay
+    arrow_origin_angle = orientation(vx, vy) + math.pi
+    # compute R1 and R2
+    alpha1 = arrow_origin_angle + ARROW_ANGLE
+    alpha2 = arrow_origin_angle - ARROW_ANGLE
+    R1 = (bx + ARROW_RADIUS*math.cos(alpha1), by - ARROW_RADIUS*math.sin(alpha1))
+    R2 = (bx + ARROW_RADIUS*math.cos(alpha2), by - ARROW_RADIUS*math.sin(alpha2))
+    return (R1, R2)
+
+
+
+class GraphViewerNode():
+    def __init__(self, x, y, name, gdb_value):
+        self.x = x
+        self.y = y
+        self.width = 30
+        self.height = 30
+        self.name = name
+        self.gdb_value = gdb_value
+        self.connections = [] # outgoing pointers to other nodes, to draw arrows
+        self.fill_color = (0.8, 0.4, 0)
+
+
+
+class GraphViewer(gtk.DrawingArea):
+    def __init__(self):
+        super(gtk.DrawingArea, self).__init__()
+        self.nodes = []
+        self.connect("expose_event", self.expose)
+
+    def expose(self, widget, event):
+        context = pangocairo.CairoContext(widget.window.cairo_create())
+        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
+        context.clip()
+        self.draw(context)
+        return False
+
+
+    def draw_node(self, context, node):
+        # draw node name
+        name_layout = context.create_layout()
+        name_layout.set_font_description(pango.FontDescription("sans 8"))
+        name_layout.set_text(node.name)
+        _, name_height = name_layout.get_pixel_size()
+        context.move_to(node.x, node.y - name_height)
+        context.show_layout(name_layout)
+
+        # draw node rect
+        context.rectangle(node.x, node.y, node.width, node.height)
+        context.set_source_rgb(*node.fill_color)
+        context.fill()
+        context.set_source_rgb(0,0,0)
+        context.set_line_width(1)
+        context.rectangle(node.x, node.y, node.width, node.height)
+        context.stroke()
+
+
+    def draw_connection(self, context, n1, n2):
+        (p1, p2) = node_connection_points(n1, n2)
+        context.move_to(*p1)
+        context.line_to(*p2)
+        context.stroke()
+        (a1, a2) = arrow_points(p1, p2)
+        context.move_to(*a1)
+        context.line_to(*p2)
+        context.line_to(*a2)
+        context.stroke()
+
+    def draw(self, context):
+        rect = self.get_allocation() # widget dimensions?
+
+        for node in self.nodes:
+            self.draw_node(context, node)
+
+        for node in self.nodes:
+            for other in node.connections:
+                self.draw_connection(context, node, other)
+        
+
+class ViewerWindow(gtk.Window):
+    def __init__(self):
+        super(gtk.Window, self).__init__()
+        self.entry = gtk.Entry()
+        self.viewer = GraphViewer()
+        self.properties = gtk.TreeView()
+        self.vbox = gtk.VBox()
+        self.paned = gtk.HPaned()
+        labelhbox = gtk.HBox()
+        labelhbox.pack_start(gtk.Label("Expression:"), expand = False)
+        labelhbox.pack_start(self.entry)
+        self.vbox.pack_start(labelhbox, expand = False)
+        self.vbox.pack_start(self.paned)
+        self.paned.add1(self.viewer)
+        self.paned.add2(self.properties)
+        self.add(self.vbox)
+
+        self.set_default_size(1000,600)
+        self.paned.set_position(750)
+
+def merge_dict(dst, src):
+    for i in src:
+        dst[i] = src[i]
+
+def visit_values(value, x, y, recursion_level):
+    """
+    Recursively traverse children of a gdb Value, storing pairs (address, GraphViewerNode).
+    returns (width, height, node_dict)
+    """
+    # TODO: CHECK IF NODE HAS ALREADY BEEN VISITED, DON'T CREATE REPEATED NODES :P
+    NODE_SPACING = 100
+    if recursion_level < 0:
+        return NODE_SPACING, NODE_SPACING, {str(value.address): GraphViewerNode(x, y, "...", value)}
+
+    result = {}
+    result[str(value.address)] = GraphViewerNode(x, y, str(value.type), value)
+    width = NODE_SPACING
+    height = 0
+    children_x = x+NODE_SPACING
+    children_y = y
+    children_height = 0
+    if value.type.code == gdb.TYPE_CODE_PTR and value != 0:
+        pointee = value.dereference()
+        pointee_width, pointee_height, pointee_nodes = visit_values(pointee, children_x, children_y, recursion_level - 1)
+        merge_dict(result, pointee_nodes)
+        return NODE_SPACING+pointee_width, max(NODE_SPACING, pointee_height), result
+    elif len(value.type.fields()) > 0: 
+        for f in value.type.fields():
+            child = value[f.name]
+            if child.type.code == gdb.TYPE_CODE_PTR:
+                if child != 0:
+                    child_width, child_height, child_nodes = visit_values(child.dereference(), children_x, children_y, recursion_level - 1)
+                    children_y += child_height
+                    children_height += child_height
+                    width = max(width, NODE_SPACING + child_width)
+                    merge_dict(result, child_nodes)
+                else:
+                    pass # TODO: handle nulls
+        height = max(NODE_SPACING, children_height)
+        return width, height, result
+    else:
+        return NODE_SPACING, NODE_SPACING, result
+
+
+
+def create_viewer_nodes(value, recursion_level):
+    # create the nodes
+    width, height, nodes = visit_values(value, 50, 50, recursion_level)
+
+    # create connections between nodes when all the nodes are there
+    for addr in nodes:
+        value = nodes[addr].gdb_value
+        if value.type.code == gdb.TYPE_CODE_PTR and value != 0:
+            nodes[addr].connections.append(nodes[str(value)])
+        else:
+            for f in value.type.fields():
+                child = value[f.name]
+                if child.type.code == gdb.TYPE_CODE_PTR and child != 0:
+                    if str(child) in nodes:
+                        nodes[addr].connections.append(nodes[str(child)])
+
+    return nodes.values()
+
+
+
+
+
+
+class ViewerCommand(gdb.Command):
+    """View data structures"""
+    def __init__(self):
+        super(ViewerCommand, self).__init__("view", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
+
+
+    def invoke(self, arg, from_tty):
+        args = gdb.string_to_argv(arg)
+        value = gdb.parse_and_eval(args[0])
+        if len(args) > 1:
+            recursion_level = int(args[1])
+        else:
+            recursion_level = 10
+
+        try:
+            window = ViewerWindow()
+            window.connect("destroy", gtk.main_quit)
+            window.show_all()
+
+            nodes = create_viewer_nodes(value, recursion_level)
+            window.viewer.nodes = nodes
+            window.viewer.queue_draw()
+            gtk.main()
+        except:
+            print "trololo"
+            window.destroy()
+            raise
+
+        #print print_node(value.dereference(), recursion_level)
+
+
+        
+
+
+
+ViewerCommand()
+
+